From cae01c6e0f28cb7bc3836e63df0b173b68ff988c Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:52:04 +0200 Subject: [PATCH 01/83] Fix flood of Angular warning messages on policies page (#16618) Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --- .../policies/policies.component.html | 23 ++++------ .../policies/policies.component.ts | 46 +++++++++++++------ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.html b/apps/web/src/app/admin-console/organizations/policies/policies.component.html index ea14986749f..8df73a50e14 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.html @@ -1,7 +1,6 @@ - @let organization = organization$ | async; @if (loading) { - @for (p of policies; track p.name) { - @if (p.display$(organization, configService) | async) { - - - - @if (policiesEnabledMap.get(p.type)) { - {{ "on" | i18n }} - } - {{ p.description | i18n }} - - - } + @for (p of policies$ | async; track p.type) { + + + + @if (policiesEnabledMap.get(p.type)) { + {{ "on" | i18n }} + } + {{ p.description | i18n }} + + } diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 45383133687..e2c51b77d45 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -2,8 +2,17 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, lastValueFrom, Observable } from "rxjs"; -import { first, map } from "rxjs/operators"; +import { + combineLatest, + firstValueFrom, + lastValueFrom, + Observable, + of, + switchMap, + first, + map, + withLatestFrom, +} from "rxjs"; import { getOrganizationById, @@ -11,7 +20,6 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -39,8 +47,7 @@ import { POLICY_EDIT_REGISTER } from "./policy-register-token"; export class PoliciesComponent implements OnInit { loading = true; organizationId: string; - policies: readonly BasePolicyEditDefinition[]; - protected organization$: Observable; + policies$: Observable; private orgPolicies: PolicyResponse[]; protected policiesEnabledMap: Map = new Map(); @@ -63,28 +70,41 @@ export class PoliciesComponent implements OnInit { this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); - this.organization$ = this.organizationService + const organization$ = this.organizationService .organizations$(userId) .pipe(getOrganizationById(this.organizationId)); - this.policies = this.policyListService.getPolicies(); + this.policies$ = organization$.pipe( + withLatestFrom(of(this.policyListService.getPolicies())), + switchMap(([organization, policies]) => { + return combineLatest( + policies.map((policy) => + policy + .display$(organization, this.configService) + .pipe(map((shouldDisplay) => ({ policy, shouldDisplay }))), + ), + ); + }), + map((results) => + results.filter((result) => result.shouldDisplay).map((result) => result.policy), + ), + ); await this.load(); // Handle policies component launch from Event message - this.route.queryParams - .pipe(first()) + combineLatest([this.route.queryParams.pipe(first()), this.policies$]) /* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */ - .subscribe(async (qParams) => { + .subscribe(async ([qParams, policies]) => { if (qParams.policyId != null) { const policyIdFromEvents: string = qParams.policyId; for (const orgPolicy of this.orgPolicies) { if (orgPolicy.id === policyIdFromEvents) { - for (let i = 0; i < this.policies.length; i++) { - if (this.policies[i].type === orgPolicy.type) { + for (let i = 0; i < policies.length; i++) { + if (policies[i].type === orgPolicy.type) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.edit(this.policies[i]); + this.edit(policies[i]); break; } } From 2ccd841f5807e7086b3b141a6435b3fed232d72e Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Tue, 30 Sep 2025 07:53:10 -0400 Subject: [PATCH 02/83] feat(Utils.fromBufferToB64): [Platform/PM-26186] Add type safety and ArrayBufferView support + tests (#16609) * PM-26186 - Utils.ts - fromBufferToB64 - (1) Add type safety (2) Add ArrayBufferView support (3) Add tests * PM-26186 - Utils.ts - add overloads so that we can specify callers who pass defined buffers will always get a string back so I don't have to modify all call sites to add a null assertion or "as string" --- libs/common/src/platform/misc/utils.spec.ts | 77 ++++++++++++++++++++- libs/common/src/platform/misc/utils.ts | 67 +++++++++++++++++- 2 files changed, 140 insertions(+), 4 deletions(-) diff --git a/libs/common/src/platform/misc/utils.spec.ts b/libs/common/src/platform/misc/utils.spec.ts index 818138863fb..9f01db61fa6 100644 --- a/libs/common/src/platform/misc/utils.spec.ts +++ b/libs/common/src/platform/misc/utils.spec.ts @@ -302,7 +302,7 @@ describe("Utils Service", () => { expect(b64String).toBe(b64HelloWorldString); }); - runInBothEnvironments("should return an empty string for an empty ArrayBuffer", () => { + runInBothEnvironments("should return empty string for an empty ArrayBuffer", () => { const buffer = new Uint8Array([]).buffer; const b64String = Utils.fromBufferToB64(buffer); expect(b64String).toBe(""); @@ -312,6 +312,81 @@ describe("Utils Service", () => { const b64String = Utils.fromBufferToB64(null); expect(b64String).toBeNull(); }); + + runInBothEnvironments("returns null for undefined input", () => { + const b64 = Utils.fromBufferToB64(undefined as unknown as ArrayBuffer); + expect(b64).toBeNull(); + }); + + runInBothEnvironments("returns empty string for empty input", () => { + const b64 = Utils.fromBufferToB64(new ArrayBuffer(0)); + expect(b64).toBe(""); + }); + + runInBothEnvironments("accepts Uint8Array directly", () => { + const u8 = new Uint8Array(asciiHelloWorldArray); + const b64 = Utils.fromBufferToB64(u8); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments("respects byteOffset/byteLength (view window)", () => { + // [xx, 'hello world', yy] — view should only encode the middle slice + const prefix = [1, 2, 3]; + const suffix = [4, 5]; + const all = new Uint8Array([...prefix, ...asciiHelloWorldArray, ...suffix]); + const view = new Uint8Array(all.buffer, prefix.length, asciiHelloWorldArray.length); + const b64 = Utils.fromBufferToB64(view); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments("handles DataView (ArrayBufferView other than Uint8Array)", () => { + const u8 = new Uint8Array(asciiHelloWorldArray); + const dv = new DataView(u8.buffer, 0, u8.byteLength); + const b64 = Utils.fromBufferToB64(dv); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments("handles DataView with offset/length window", () => { + // Buffer: [xx, 'hello world', yy] + const prefix = [9, 9, 9]; + const suffix = [8, 8]; + const all = new Uint8Array([...prefix, ...asciiHelloWorldArray, ...suffix]); + + // DataView over just the "hello world" window + const dv = new DataView(all.buffer, prefix.length, asciiHelloWorldArray.length); + + const b64 = Utils.fromBufferToB64(dv); + expect(b64).toBe(b64HelloWorldString); + }); + + runInBothEnvironments( + "encodes empty view (offset-length window of zero) as empty string", + () => { + const backing = new Uint8Array([1, 2, 3, 4]); + const emptyView = new Uint8Array(backing.buffer, 2, 0); + const b64 = Utils.fromBufferToB64(emptyView); + expect(b64).toBe(""); + }, + ); + + runInBothEnvironments("does not mutate the input", () => { + const original = new Uint8Array(asciiHelloWorldArray); + const copyBefore = new Uint8Array(original); // snapshot + Utils.fromBufferToB64(original); + expect(original).toEqual(copyBefore); // unchanged + }); + + it("produces the same Base64 in Node vs non-Node mode", () => { + const bytes = new Uint8Array(asciiHelloWorldArray); + + Utils.isNode = true; + const nodeB64 = Utils.fromBufferToB64(bytes); + + Utils.isNode = false; + const browserB64 = Utils.fromBufferToB64(bytes); + + expect(browserB64).toBe(nodeB64); + }); }); describe("fromB64ToArray(...)", () => { diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index c103e346a85..43a9e43d92b 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -128,15 +128,52 @@ export class Utils { return arr; } - static fromBufferToB64(buffer: ArrayBuffer): string { + /** + * Convert binary data into a Base64 string. + * + * Overloads are provided for two categories of input: + * + * 1. ArrayBuffer + * - A raw, fixed-length chunk of memory (no element semantics). + * - Example: `const buf = new ArrayBuffer(16);` + * + * 2. ArrayBufferView + * - A *view* onto an existing buffer that gives the bytes meaning. + * - Examples: Uint8Array, Int32Array, DataView, etc. + * - Views can expose only a *window* of the underlying buffer via + * `byteOffset` and `byteLength`. + * Example: + * ```ts + * const buf = new ArrayBuffer(8); + * const full = new Uint8Array(buf); // sees all 8 bytes + * const half = new Uint8Array(buf, 4, 4); // sees only last 4 bytes + * ``` + * + * Returns: + * - Base64 string for non-empty inputs, + * - null if `buffer` is `null` or `undefined` + * - empty string if `buffer` is empty (0 bytes) + */ + static fromBufferToB64(buffer: null | undefined): null; + static fromBufferToB64(buffer: ArrayBuffer): string; + static fromBufferToB64(buffer: ArrayBufferView): string; + static fromBufferToB64(buffer: ArrayBuffer | ArrayBufferView | null | undefined): string | null { + // Handle null / undefined input if (buffer == null) { return null; } + + const bytes: Uint8Array = Utils.normalizeToUint8Array(buffer); + + // Handle empty input + if (bytes.length === 0) { + return ""; + } + if (Utils.isNode) { - return Buffer.from(buffer).toString("base64"); + return Buffer.from(bytes).toString("base64"); } else { let binary = ""; - const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } @@ -144,6 +181,30 @@ export class Utils { } } + /** + * Normalizes input into a Uint8Array so we always have a uniform, + * byte-level view of the data. This avoids dealing with differences + * between ArrayBuffer (raw memory with no indexing) and other typed + * views (which may have element sizes, offsets, and lengths). + * @param buffer ArrayBuffer or ArrayBufferView (e.g. Uint8Array, DataView, etc.) + */ + private static normalizeToUint8Array(buffer: ArrayBuffer | ArrayBufferView): Uint8Array { + /** + * 1) Uint8Array: already bytes → use directly. + * 2) ArrayBuffer: wrap whole buffer. + * 3) Other ArrayBufferView (e.g., DataView, Int32Array): + * wrap the view’s window (byteOffset..byteOffset+byteLength). + */ + if (buffer instanceof Uint8Array) { + return buffer; + } else if (buffer instanceof ArrayBuffer) { + return new Uint8Array(buffer); + } else { + const view = buffer as ArrayBufferView; + return new Uint8Array(view.buffer, view.byteOffset, view.byteLength); + } + } + static fromBufferToUrlB64(buffer: ArrayBuffer): string { return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer)); } From 54a53a1c348808c0dfeb917626887070d8b00c64 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Tue, 30 Sep 2025 06:33:32 -0600 Subject: [PATCH 03/83] Use tracing in ssh_agent (#16455) * [BEEEP][PM-255518] Use tracing for improved observability * feedback dani-garcia: use DefaultVisitor * set default log level * convert printlns in objc crate * convert printlns in autotype crate * convert printlns in autostart crate * convert printlns in core/password crate * convert printlns in core/biometric crate * convert printlns in napi crate * convert log usage in macos provider crate * convert existing log macros to tracing * fix the cargo.toml sort lint errors * Revert "fix the cargo.toml sort lint errors" This reverts commit fd149ab697d37ea8fc9c22db8f96684fc99bf2d8. * fix the sort lint using correct cargo sort version * feedback coltonhurst: more comments/clarity on behavior * revert changes to ssh_agent * Use tracing in ssh_agent --- .../desktop_native/core/src/ssh_agent/mod.rs | 23 ++++++++-------- .../ssh_agent/named_pipe_listener_stream.rs | 27 +++++++++---------- .../desktop_native/core/src/ssh_agent/unix.rs | 17 ++++++------ 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs index d038ba2277f..3440a0114ae 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs @@ -11,6 +11,7 @@ use bitwarden_russh::{ session_bind::SessionBindResult, ssh_agent::{self, SshKey}, }; +use tracing::{error, info}; #[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "unix.rs")] @@ -86,7 +87,7 @@ impl ssh_agent::Agent info: &peerinfo::models::PeerInfo, ) -> bool { if !self.is_running() { - println!("[BitwardenDesktopAgent] Agent is not running, but tried to call confirm"); + error!("Agent is not running, but tried to call confirm"); return false; } @@ -94,7 +95,7 @@ impl ssh_agent::Agent let request_data = match request_parser::parse_request(data) { Ok(data) => data, Err(e) => { - println!("[SSH Agent] Error while parsing request: {e}"); + error!(error = %e, "Error while parsing request"); return false; } }; @@ -105,12 +106,12 @@ impl ssh_agent::Agent _ => None, }; - println!( - "[SSH Agent] Confirming request from application: {}, is_forwarding: {}, namespace: {}, host_key: {}", + info!( + is_forwarding = %info.is_forwarding(), + namespace = ?namespace.as_ref(), + host_key = %STANDARD.encode(info.host_key()), + "Confirming request from application: {}", info.process_name(), - info.is_forwarding(), - namespace.clone().unwrap_or_default(), - STANDARD.encode(info.host_key()) ); let mut rx_channel = self.get_ui_response_rx.lock().await.resubscribe(); @@ -172,7 +173,7 @@ impl ssh_agent::Agent connection_info.set_host_key(session_bind_info.host_key.clone()); } SessionBindResult::SignatureFailure => { - println!("[BitwardenDesktopAgent] Session bind failure: Signature failure"); + error!("Session bind failure: Signature failure"); } } } @@ -181,7 +182,7 @@ impl ssh_agent::Agent impl BitwardenDesktopAgent { pub fn stop(&self) { if !self.is_running() { - println!("[BitwardenDesktopAgent] Tried to stop agent while it is not running"); + error!("Tried to stop agent while it is not running"); return; } @@ -227,7 +228,7 @@ impl BitwardenDesktopAgent { ); } Err(e) => { - eprintln!("[SSH Agent Native Module] Error while parsing key: {e}"); + error!(error=%e, "Error while parsing key"); } } } @@ -265,7 +266,7 @@ impl BitwardenDesktopAgent { fn get_request_id(&self) -> u32 { if !self.is_running() { - println!("[BitwardenDesktopAgent] Agent is not running, but tried to get request id"); + error!("Agent is not running, but tried to get request id"); return 0; } diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs index fccd7ca5ed6..cb10e873a33 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs @@ -14,6 +14,7 @@ use tokio::{ select, }; use tokio_util::sync::CancellationToken; +use tracing::{error, info}; use windows::Win32::{Foundation::HANDLE, System::Pipes::GetNamedPipeClientProcessId}; use crate::ssh_agent::peerinfo::{self, models::PeerInfo}; @@ -31,42 +32,38 @@ impl NamedPipeServerStream { pub fn new(cancellation_token: CancellationToken, is_running: Arc) -> Self { let (tx, rx) = tokio::sync::mpsc::channel(16); tokio::spawn(async move { - println!( - "[SSH Agent Native Module] Creating named pipe server on {}", - PIPE_NAME - ); + info!("Creating named pipe server on {}", PIPE_NAME); let mut listener = match ServerOptions::new().create(PIPE_NAME) { Ok(pipe) => pipe, - Err(err) => { - println!("[SSH Agent Native Module] Encountered an error creating the first pipe. The system's openssh service must likely be disabled"); - println!("[SSH Agent Natvie Module] error: {}", err); + Err(e) => { + error!(error = %e, "Encountered an error creating the first pipe. The system's openssh service must likely be disabled"); cancellation_token.cancel(); is_running.store(false, Ordering::Relaxed); return; } }; loop { - println!("[SSH Agent Native Module] Waiting for connection"); + info!("Waiting for connection"); select! { _ = cancellation_token.cancelled() => { - println!("[SSH Agent Native Module] Cancellation token triggered, stopping named pipe server"); + info!("[SSH Agent Native Module] Cancellation token triggered, stopping named pipe server"); break; } _ = listener.connect() => { - println!("[SSH Agent Native Module] Incoming connection"); + info!("[SSH Agent Native Module] Incoming connection"); let handle = HANDLE(listener.as_raw_handle()); let mut pid = 0; unsafe { if let Err(e) = GetNamedPipeClientProcessId(handle, &mut pid) { - println!("Error getting named pipe client process id {}", e); + error!(error = %e, pid, "Faile to get named pipe client process id"); continue } }; let peer_info = peerinfo::gather::get_peer_info(pid); let peer_info = match peer_info { - Err(err) => { - println!("Failed getting process info for pid {} {}", pid, err); + Err(e) => { + error!(error = %e, pid = %pid, "Failed getting process info"); continue }, Ok(info) => info, @@ -76,8 +73,8 @@ impl NamedPipeServerStream { listener = match ServerOptions::new().create(PIPE_NAME) { Ok(pipe) => pipe, - Err(err) => { - println!("[SSH Agent Native Module] Encountered an error creating a new pipe {}", err); + Err(e) => { + error!(error = %e, "Encountered an error creating a new pipe"); cancellation_token.cancel(); is_running.store(false, Ordering::Relaxed); return; diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs index 53142d4c476..813ebd61cc1 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs @@ -12,6 +12,7 @@ use bitwarden_russh::ssh_agent; use homedir::my_home; use tokio::{net::UnixListener, sync::Mutex}; use tokio_util::sync::CancellationToken; +use tracing::{error, info}; use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream; @@ -36,14 +37,12 @@ impl BitwardenDesktopAgent { let ssh_path = match std::env::var("BITWARDEN_SSH_AUTH_SOCK") { Ok(path) => path, Err(_) => { - println!("[SSH Agent Native Module] BITWARDEN_SSH_AUTH_SOCK not set, using default path"); + info!("BITWARDEN_SSH_AUTH_SOCK not set, using default path"); let ssh_agent_directory = match my_home() { Ok(Some(home)) => home, _ => { - println!( - "[SSH Agent Native Module] Could not determine home directory" - ); + info!("Could not determine home directory"); return; } }; @@ -65,10 +64,10 @@ impl BitwardenDesktopAgent { } }; - println!("[SSH Agent Native Module] Starting SSH Agent server on {ssh_path:?}"); + info!(socket = %ssh_path, "Starting SSH Agent server"); let sockname = std::path::Path::new(&ssh_path); if let Err(e) = std::fs::remove_file(sockname) { - println!("[SSH Agent Native Module] Could not remove existing socket file: {e}"); + error!(error = %e, socket = %ssh_path, "Could not remove existing socket file"); if e.kind() != std::io::ErrorKind::NotFound { return; } @@ -79,7 +78,7 @@ impl BitwardenDesktopAgent { // Only the current user should be able to access the socket if let Err(e) = fs::set_permissions(sockname, fs::Permissions::from_mode(0o600)) { - println!("[SSH Agent Native Module] Could not set socket permissions: {e}"); + error!(error = %e, socket = ?sockname, "Could not set socket permissions"); return; } @@ -100,10 +99,10 @@ impl BitwardenDesktopAgent { cloned_agent_state .is_running .store(false, std::sync::atomic::Ordering::Relaxed); - println!("[SSH Agent Native Module] SSH Agent server exited"); + info!("SSH Agent server exited"); } Err(e) => { - eprintln!("[SSH Agent Native Module] Error while starting agent server: {e}"); + error!(error = %e, socket = %ssh_path, "Unable to start start agent server"); } } }); From 25020ced5d18f30965e46ddb87df4c77c2d176d2 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:37:00 +0200 Subject: [PATCH 04/83] [PM-23251] Remove low-kdf banner (#16511) * Remove low-kdf banner * update tests --- .../services/vault-banners.service.spec.ts | 71 +------------------ .../services/vault-banners.service.ts | 33 +-------- .../vault-banners.component.html | 12 ---- .../vault-banners.component.spec.ts | 7 -- .../vault-banners/vault-banners.component.ts | 2 - apps/web/src/locales/en/messages.json | 12 ---- 6 files changed, 2 insertions(+), 135 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts index 89a3757e939..6b46cd89956 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts @@ -1,14 +1,8 @@ import { TestBed } from "@angular/core/testing"; import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs"; -import { - AuthRequestServiceAbstraction, - UserDecryptionOptions, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; +import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; -import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { DeviceType } from "@bitwarden/common/enums"; @@ -18,7 +12,6 @@ import { StateProvider } from "@bitwarden/common/platform/state"; import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { KdfConfigService, KdfType } from "@bitwarden/key-management"; import { PREMIUM_BANNER_REPROMPT_KEY, @@ -34,14 +27,9 @@ describe("VaultBannersService", () => { const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); const getEmailVerified = jest.fn().mockResolvedValue(true); const lastSync$ = new BehaviorSubject(null); - const userDecryptionOptions$ = new BehaviorSubject({ - hasMasterPassword: true, - }); - const kdfConfig$ = new BehaviorSubject({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600000 }); const accounts$ = new BehaviorSubject>({ [userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo, }); - const devices$ = new BehaviorSubject([]); const pendingAuthRequests$ = new BehaviorSubject>([]); beforeEach(() => { @@ -64,32 +52,14 @@ describe("VaultBannersService", () => { provide: StateProvider, useValue: fakeStateProvider, }, - { - provide: PlatformUtilsService, - useValue: { isSelfHost }, - }, { provide: AccountService, useValue: { accounts$ }, }, - { - provide: KdfConfigService, - useValue: { getKdfConfig$: () => kdfConfig$ }, - }, { provide: SyncService, useValue: { lastSync$: () => lastSync$ }, }, - { - provide: UserDecryptionOptionsServiceAbstraction, - useValue: { - userDecryptionOptionsById$: () => userDecryptionOptions$, - }, - }, - { - provide: DevicesServiceAbstraction, - useValue: { getDevices$: () => devices$ }, - }, { provide: AuthRequestServiceAbstraction, useValue: { getPendingAuthRequests$: () => pendingAuthRequests$ }, @@ -197,45 +167,6 @@ describe("VaultBannersService", () => { }); }); - describe("KDFSettings", () => { - beforeEach(async () => { - userDecryptionOptions$.next({ hasMasterPassword: true }); - kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 599999 }); - }); - - it("shows low KDF iteration banner", async () => { - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); - }); - - it("does not show low KDF iteration banner if KDF type is not PBKDF2_SHA256", async () => { - kdfConfig$.next({ kdfType: KdfType.Argon2id, iterations: 600001 }); - - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); - }); - - it("does not show low KDF for iterations about 600,000", async () => { - kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600001 }); - - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); - }); - - it("dismisses low KDF iteration banner", async () => { - service = TestBed.inject(VaultBannersService); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); - - await service.dismissBanner(userId, VisibleVaultBanner.KDFSettings); - - expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); - }); - }); - describe("OutdatedBrowser", () => { beforeEach(async () => { // Hardcode `MSIE` in userAgent string diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index c4396940998..1c53274d9d7 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -1,10 +1,7 @@ import { Injectable } from "@angular/core"; import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs"; -import { - AuthRequestServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; +import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -18,10 +15,8 @@ import { import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; -import { PBKDF2KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"; export const VisibleVaultBanner = { - KDFSettings: "kdf-settings", OutdatedBrowser: "outdated-browser", Premium: "premium", VerifyEmail: "verify-email", @@ -64,9 +59,7 @@ export class VaultBannersService { private stateProvider: StateProvider, private billingAccountProfileStateService: BillingAccountProfileStateService, private platformUtilsService: PlatformUtilsService, - private kdfConfigService: KdfConfigService, private syncService: SyncService, - private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private authRequestService: AuthRequestServiceAbstraction, ) {} @@ -133,21 +126,6 @@ export class VaultBannersService { return needsVerification && !alreadyDismissed; } - /** Returns true when the low KDF iteration banner should be shown */ - async shouldShowLowKDFBanner(userId: UserId): Promise { - const hasLowKDF = ( - await firstValueFrom(this.userDecryptionOptionsService.userDecryptionOptionsById$(userId)) - )?.hasMasterPassword - ? await this.isLowKdfIteration(userId) - : false; - - const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( - VisibleVaultBanner.KDFSettings, - ); - - return hasLowKDF && !alreadyDismissed; - } - /** Dismiss the given banner and perform any respective side effects */ async dismissBanner(userId: UserId, banner: SessionBanners): Promise { if (banner === VisibleVaultBanner.Premium) { @@ -221,13 +199,4 @@ export class VaultBannersService { }; }); } - - private async isLowKdfIteration(userId: UserId) { - const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId)); - return ( - kdfConfig != null && - kdfConfig.kdfType === KdfType.PBKDF2_SHA256 && - kdfConfig.iterations < PBKDF2KdfConfig.ITERATIONS.defaultValue - ); - } } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html index d52ea9f61e6..44b2975ee19 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html @@ -25,18 +25,6 @@ - - {{ "lowKDFIterationsBanner" | i18n }} - - {{ "changeKDFSettings" | i18n }} - - - { shouldShowPremiumBanner$: jest.fn((userId: UserId) => premiumBanner$), shouldShowUpdateBrowserBanner: jest.fn(), shouldShowVerifyEmailBanner: jest.fn(), - shouldShowLowKDFBanner: jest.fn(), shouldShowPendingAuthRequestBanner: jest.fn((userId: UserId) => Promise.resolve(pendingAuthRequest$.value), ), @@ -48,7 +47,6 @@ describe("VaultBannersComponent", () => { messageSubject = new Subject<{ command: string }>(); bannerService.shouldShowUpdateBrowserBanner.mockResolvedValue(false); bannerService.shouldShowVerifyEmailBanner.mockResolvedValue(false); - bannerService.shouldShowLowKDFBanner.mockResolvedValue(false); pendingAuthRequest$.next(false); premiumBanner$.next(false); @@ -137,11 +135,6 @@ describe("VaultBannersComponent", () => { method: bannerService.shouldShowVerifyEmailBanner, banner: VisibleVaultBanner.VerifyEmail, }, - { - name: "LowKDF", - method: bannerService.shouldShowLowKDFBanner, - banner: VisibleVaultBanner.KDFSettings, - }, ].forEach(({ name, method, banner }) => { describe(name, () => { beforeEach(async () => { diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index a16374f19b3..011e7a3bce6 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -100,14 +100,12 @@ export class VaultBannersComponent implements OnInit { const showBrowserOutdated = await this.vaultBannerService.shouldShowUpdateBrowserBanner(activeUserId); const showVerifyEmail = await this.vaultBannerService.shouldShowVerifyEmailBanner(activeUserId); - const showLowKdf = await this.vaultBannerService.shouldShowLowKDFBanner(activeUserId); const showPendingAuthRequest = await this.vaultBannerService.shouldShowPendingAuthRequestBanner(activeUserId); this.visibleBanners = [ showBrowserOutdated ? VisibleVaultBanner.OutdatedBrowser : null, showVerifyEmail ? VisibleVaultBanner.VerifyEmail : null, - showLowKdf ? VisibleVaultBanner.KDFSettings : null, showPendingAuthRequest ? VisibleVaultBanner.PendingAuthRequest : null, ].filter((banner) => banner !== null); } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 1b374c97478..e2bb463c939 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8408,12 +8408,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -10031,12 +10025,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, From c93586a0aab2b1654a196475ac25d2686d84d035 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:56:29 +0200 Subject: [PATCH 05/83] [deps] Tools: Update jsdom to v27 (#16634) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 242 +++++++++++++++++++++++++++++------------- package.json | 2 +- 3 files changed, 169 insertions(+), 77 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 659a68d13a5..4ed72a9c21b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -73,7 +73,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", diff --git a/package-lock.json b/package-lock.json index 1b126255e63..5c325844a94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", @@ -208,7 +208,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", @@ -2586,23 +2586,54 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", + "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.1" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.5.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.6.tgz", + "integrity": "sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.1" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" }, "node_modules/@babel/code-frame": { "version": "7.27.1", @@ -5378,9 +5409,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "funding": [ { "type": "github", @@ -5420,9 +5451,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "funding": [ { "type": "github", @@ -5435,7 +5466,7 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.0.2", + "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "engines": { @@ -5468,6 +5499,28 @@ "@csstools/css-tokenizer": "^3.0.4" } }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", @@ -16919,6 +16972,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -19109,6 +19171,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -19150,16 +19225,17 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.5.0.tgz", - "integrity": "sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", + "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/csstype": { @@ -19183,16 +19259,16 @@ } }, "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", "license": "MIT", "dependencies": { "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "whatwg-url": "^15.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/data-view-buffer": { @@ -26910,34 +26986,34 @@ } }, "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", "license": "MIT", "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", + "@asamuzakjp/dom-selector": "^6.5.4", + "cssstyle": "^5.3.0", + "data-urls": "^6.0.0", "decimal.js": "^10.5.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", + "parse5": "^7.3.0", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", + "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", + "webidl-conversions": "^8.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", + "whatwg-url": "^15.0.0", + "ws": "^8.18.2", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "peerDependencies": { "canvas": "^3.0.0" @@ -26949,35 +27025,38 @@ } }, "node_modules/jsdom/node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", + "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.86" + "tldts-core": "^7.0.16" }, "bin": { "tldts": "bin/cli.js" } }, - "node_modules/jsdom/node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "license": "MIT" - }, "node_modules/jsdom/node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "license": "BSD-3-Clause", "dependencies": { - "tldts": "^6.1.32" + "tldts": "^7.0.5" }, "engines": { "node": ">=16" } }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -28937,6 +29016,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -30268,7 +30353,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -31424,6 +31508,7 @@ "version": "2.2.20", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, "license": "MIT" }, "node_modules/nx": { @@ -33413,7 +33498,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -34711,7 +34795,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -36071,7 +36154,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -37597,9 +37679,9 @@ } }, "node_modules/tldts-core": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.9.tgz", - "integrity": "sha512-/FGY1+CryHsxF9SFiPZlMOcwQsfABkAvOJO5VEKE8TNifVEqgMF7+UVXHGhm1z4gPUfvVS/EYcwhiRU3vUa1ag==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", + "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", "license": "MIT" }, "node_modules/tmp": { @@ -37673,15 +37755,15 @@ } }, "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/tree-dump": { @@ -39867,6 +39949,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -40807,16 +40890,25 @@ } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" } }, "node_modules/which": { diff --git a/package.json b/package.json index e94d0e98522..f5894a04da9 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.1.0", + "jsdom": "27.0.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", From 7848b7d480b7489cf9a99543088c920613545ab5 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:40:00 +0200 Subject: [PATCH 06/83] Revert "[deps] Tools: Update jsdom to v27 (#16634)" (#16666) This reverts commit c93586a0aab2b1654a196475ac25d2686d84d035. --- apps/cli/package.json | 2 +- package-lock.json | 242 +++++++++++++----------------------------- package.json | 2 +- 3 files changed, 77 insertions(+), 169 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 4ed72a9c21b..659a68d13a5 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -73,7 +73,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "27.0.0", + "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", diff --git a/package-lock.json b/package-lock.json index 5c325844a94..1b126255e63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "27.0.0", + "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", @@ -208,7 +208,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "27.0.0", + "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", @@ -2586,54 +2586,23 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", - "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.1" + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "6.5.6", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.6.tgz", - "integrity": "sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==", - "license": "MIT", - "dependencies": { - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.1.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.1" - } - }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "license": "MIT" + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/@babel/code-frame": { "version": "7.27.1", @@ -5409,9 +5378,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", "funding": [ { "type": "github", @@ -5451,9 +5420,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", "funding": [ { "type": "github", @@ -5466,7 +5435,7 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", + "@csstools/color-helpers": "^5.0.2", "@csstools/css-calc": "^2.1.4" }, "engines": { @@ -5499,28 +5468,6 @@ "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", - "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", @@ -16972,15 +16919,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -19171,19 +19109,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -19225,17 +19150,16 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", - "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.5.0.tgz", + "integrity": "sha512-/7gw8TGrvH/0g564EnhgFZogTMVe+lifpB7LWU+PEsiq5o83TUXR3fDbzTRXOJhoJwck5IS9ez3Em5LNMMO2aw==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^4.0.3", - "@csstools/css-syntax-patches-for-csstree": "^1.0.14", - "css-tree": "^3.1.0" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/csstype": { @@ -19259,16 +19183,16 @@ } }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "license": "MIT", "dependencies": { "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-url": "^14.0.0" }, "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/data-view-buffer": { @@ -26986,34 +26910,34 @@ } }, "node_modules/jsdom": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", - "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "license": "MIT", "dependencies": { - "@asamuzakjp/dom-selector": "^6.5.4", - "cssstyle": "^5.3.0", - "data-urls": "^6.0.0", + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", "decimal.js": "^10.5.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "parse5": "^7.3.0", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.0", + "tough-cookie": "^5.1.1", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.0", + "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0", - "ws": "^8.18.2", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=20" + "node": ">=18" }, "peerDependencies": { "canvas": "^3.0.0" @@ -27025,38 +26949,35 @@ } }, "node_modules/jsdom/node_modules/tldts": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", - "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "license": "MIT", "dependencies": { - "tldts-core": "^7.0.16" + "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, + "node_modules/jsdom/node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, "node_modules/jsdom/node_modules/tough-cookie": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", - "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "license": "BSD-3-Clause", "dependencies": { - "tldts": "^7.0.5" + "tldts": "^6.1.32" }, "engines": { "node": ">=16" } }, - "node_modules/jsdom/node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -29016,12 +28937,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "license": "CC0-1.0" - }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -30353,6 +30268,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -31508,7 +31424,6 @@ "version": "2.2.20", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", - "dev": true, "license": "MIT" }, "node_modules/nx": { @@ -33498,6 +33413,7 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -34795,6 +34711,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -36154,6 +36071,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -37679,9 +37597,9 @@ } }, "node_modules/tldts-core": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", - "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.9.tgz", + "integrity": "sha512-/FGY1+CryHsxF9SFiPZlMOcwQsfABkAvOJO5VEKE8TNifVEqgMF7+UVXHGhm1z4gPUfvVS/EYcwhiRU3vUa1ag==", "license": "MIT" }, "node_modules/tmp": { @@ -37755,15 +37673,15 @@ } }, "node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/tree-dump": { @@ -39949,7 +39867,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -40890,25 +40807,16 @@ } }, "node_modules/whatwg-url": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", - "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.0" + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/which": { diff --git a/package.json b/package.json index f5894a04da9..e94d0e98522 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "form-data": "4.0.4", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "27.0.0", + "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", From 727689d827589e1fccb5eed88ef017be83da8988 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 30 Sep 2025 09:45:04 -0500 Subject: [PATCH 07/83] [PM-24534] Archive via CLI (#16502) * refactor `canInteract` into a component level usage. - The default service is going to be used in the CLI which won't make use of the UI-related aspects * all nested entities to be imported from the vault * initial add of archive command to the cli * add archive to oss serve * check for deleted cipher when attempting to archive * add searchability/list functionality for archived ciphers * restore an archived cipher * unarchive a cipher when a user is editing it and has lost their premium status * add missing feature flags * re-export only needed services from the vault * add needed await * add prompt when applicable for editing an archived cipher * move cipher archive service into `common/vault` * fix testing code --- .../src/popup/services/services.module.ts | 13 +-- .../item-more-options.component.ts | 3 +- .../vault-popup-items.service.spec.ts | 2 +- .../services/vault-popup-items.service.ts | 2 +- .../vault/popup/settings/archive.component.ts | 35 +++++- .../settings/vault-settings-v2.component.ts | 2 +- apps/cli/src/commands/edit.command.ts | 50 ++++++++ apps/cli/src/commands/list.command.ts | 34 +++++- apps/cli/src/commands/restore.command.ts | 33 +++++- apps/cli/src/commands/serve.command.ts | 2 +- apps/cli/src/oss-serve-configurator.ts | 34 +++++- apps/cli/src/program.ts | 8 ++ apps/cli/src/register-oss-programs.ts | 2 +- .../service-container/service-container.ts | 10 ++ apps/cli/src/vault.program.ts | 79 +++++++++++-- apps/cli/src/vault/archive.command.ts | 109 ++++++++++++++++++ .../vault-filter/vault-filter.component.ts | 2 +- .../components/vault-filter.component.ts | 2 +- .../vault/individual-vault/vault.component.ts | 2 +- .../bit-cli/src/bit-serve-configurator.ts | 4 +- .../src/services/jslib-services.module.ts | 11 +- .../abstractions/cipher-archive.service.ts | 2 - .../src/vault/abstractions/search.service.ts | 1 + .../default-cipher-archive.service.spec.ts | 53 --------- .../default-cipher-archive.service.ts | 25 +--- .../src/vault/services/search.service.ts | 10 +- libs/vault/src/index.ts | 2 - 27 files changed, 401 insertions(+), 131 deletions(-) create mode 100644 apps/cli/src/vault/archive.command.ts rename libs/{vault/src => common/src/vault}/abstractions/cipher-archive.service.ts (81%) rename libs/{vault/src => common/src/vault}/services/default-cipher-archive.service.spec.ts (78%) rename libs/{vault/src => common/src/vault}/services/default-cipher-archive.service.ts (83%) diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index d87c9417c85..ef4dd0be090 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -119,10 +119,12 @@ import { SystemNotificationsService } from "@bitwarden/common/platform/system-no import { UnsupportedSystemNotificationsService } from "@bitwarden/common/platform/system-notifications/unsupported-system-notifications.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; +import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { @@ -145,8 +147,6 @@ import { DefaultSshImportPromptService, PasswordRepromptService, SshImportPromptService, - CipherArchiveService, - DefaultCipherArchiveService, } from "@bitwarden/vault"; import { AccountSwitcherService } from "../../auth/popup/account-switching/services/account-switcher.service"; @@ -708,14 +708,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: CipherArchiveService, useClass: DefaultCipherArchiveService, - deps: [ - CipherService, - ApiService, - DialogService, - PasswordRepromptService, - BillingAccountProfileStateService, - ConfigService, - ], + deps: [CipherService, ApiService, BillingAccountProfileStateService, ConfigService], }), ]; diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 6979f519f2d..324ea2ffcdf 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -13,6 +13,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; @@ -28,7 +29,7 @@ import { MenuModule, ToastService, } from "@bitwarden/components"; -import { CipherArchiveService, PasswordRepromptService } from "@bitwarden/vault"; +import { PasswordRepromptService } from "@bitwarden/vault"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 6499719b64f..513e159f7aa 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -14,6 +14,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; import { mockAccountServiceWith, ObservableTracker } from "@bitwarden/common/spec"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; @@ -26,7 +27,6 @@ import { RestrictedItemTypesService, } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; -import { CipherArchiveService } from "@bitwarden/vault"; import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index 3e4b793737e..fa56b45c080 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -26,6 +26,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; @@ -35,7 +36,6 @@ import { CipherViewLike, CipherViewLikeUtils, } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; -import { CipherArchiveService } from "@bitwarden/vault"; import { runInsideAngular } from "../../../platform/browser/run-inside-angular.operator"; import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service"; diff --git a/apps/browser/src/vault/popup/settings/archive.component.ts b/apps/browser/src/vault/popup/settings/archive.component.ts index c3e078a9274..d685beb0287 100644 --- a/apps/browser/src/vault/popup/settings/archive.component.ts +++ b/apps/browser/src/vault/popup/settings/archive.component.ts @@ -9,6 +9,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -22,7 +23,11 @@ import { ToastService, TypographyModule, } from "@bitwarden/components"; -import { CanDeleteCipherDirective, CipherArchiveService } from "@bitwarden/vault"; +import { + CanDeleteCipherDirective, + DecryptionFailureDialogComponent, + PasswordRepromptService, +} from "@bitwarden/vault"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -56,6 +61,7 @@ export class ArchiveComponent { private toastService = inject(ToastService); private i18nService = inject(I18nService); private cipherArchiveService = inject(CipherArchiveService); + private passwordRepromptService = inject(PasswordRepromptService); private userId$: Observable = this.accountService.activeAccount$.pipe(getUserId); @@ -69,7 +75,7 @@ export class ArchiveComponent { ); async view(cipher: CipherView) { - if (!(await this.cipherArchiveService.canInteract(cipher))) { + if (!(await this.canInteract(cipher))) { return; } @@ -79,7 +85,7 @@ export class ArchiveComponent { } async edit(cipher: CipherView) { - if (!(await this.cipherArchiveService.canInteract(cipher))) { + if (!(await this.canInteract(cipher))) { return; } @@ -89,7 +95,7 @@ export class ArchiveComponent { } async delete(cipher: CipherView) { - if (!(await this.cipherArchiveService.canInteract(cipher))) { + if (!(await this.canInteract(cipher))) { return; } const confirmed = await this.dialogService.openSimpleDialog({ @@ -118,7 +124,7 @@ export class ArchiveComponent { } async unarchive(cipher: CipherView) { - if (!(await this.cipherArchiveService.canInteract(cipher))) { + if (!(await this.canInteract(cipher))) { return; } const activeUserId = await firstValueFrom(this.userId$); @@ -132,7 +138,7 @@ export class ArchiveComponent { } async clone(cipher: CipherView) { - if (!(await this.cipherArchiveService.canInteract(cipher))) { + if (!(await this.canInteract(cipher))) { return; } @@ -156,4 +162,21 @@ export class ArchiveComponent { }, }); } + + /** + * Check if the user is able to interact with the cipher + * (password re-prompt / decryption failure checks). + * @param cipher + * @private + */ + private canInteract(cipher: CipherView) { + if (cipher.decryptionFailure) { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: [cipher.id as CipherId], + }); + return false; + } + + return this.passwordRepromptService.passwordRepromptCheck(cipher); + } } diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index 4e8a49b2591..92cbf951ead 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -9,9 +9,9 @@ import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { BadgeComponent, ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; -import { CipherArchiveService } from "@bitwarden/vault"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/browser/browser-popup-utils"; diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 92674aa3dcd..f4216196ead 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import * as inquirer from "inquirer"; import { firstValueFrom, map, switchMap } from "rxjs"; import { UpdateCollectionRequest } from "@bitwarden/admin-console/common"; @@ -9,6 +10,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; @@ -40,6 +42,7 @@ export class EditCommand { private accountService: AccountService, private cliRestrictedItemTypesService: CliRestrictedItemTypesService, private policyService: PolicyService, + private billingAccountProfileStateService: BillingAccountProfileStateService, ) {} async run( @@ -92,6 +95,10 @@ export class EditCommand { private async editCipher(id: string, req: CipherExport) { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipher = await this.cipherService.get(id, activeUserId); + const hasPremium = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), + ); + if (cipher == null) { return Response.notFound(); } @@ -102,6 +109,17 @@ export class EditCommand { } cipherView = CipherExport.toView(req, cipherView); + // When a user is editing an archived cipher and does not have premium, automatically unarchive it + if (cipherView.isArchived && !hasPremium) { + const acceptedPrompt = await this.promptForArchiveEdit(); + + if (!acceptedPrompt) { + return Response.error("Edit cancelled."); + } + + cipherView.archivedDate = null; + } + const isCipherRestricted = await this.cliRestrictedItemTypesService.isCipherRestricted(cipherView); if (isCipherRestricted) { @@ -240,6 +258,38 @@ export class EditCommand { return Response.error(e); } } + + /** Prompt the user to accept movement of their cipher back to the their vault. */ + private async promptForArchiveEdit(): Promise { + // When running in serve or no interaction mode, automatically accept the prompt + if (process.env.BW_SERVE === "true" || process.env.BW_NOINTERACTION === "true") { + CliUtils.writeLn( + "Archive is only available with a Premium subscription, which has ended. Your edit was saved and the item was moved back to your vault.", + ); + return true; + } + + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "list", + name: "confirm", + message: + "When you edit and save details for an archived item without a Premium subscription, it'll be moved from your archive back to your vault.", + choices: [ + { + name: "Move now", + value: "confirmed", + }, + { + name: "Cancel", + value: "cancel", + }, + ], + }); + + return answer.confirm === "confirmed"; + } } class Options { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index d8b4cfcfd10..49527f6bf78 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -16,6 +16,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EventType } from "@bitwarden/common/enums"; import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; @@ -45,6 +46,7 @@ export class ListCommand { private accountService: AccountService, private keyService: KeyService, private cliRestrictedItemTypesService: CliRestrictedItemTypesService, + private cipherArchiveService: CipherArchiveService, ) {} async run(object: string, cmdOptions: Record): Promise { @@ -71,8 +73,13 @@ export class ListCommand { let ciphers: CipherView[]; const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const userCanArchive = await firstValueFrom( + this.cipherArchiveService.userCanArchive$(activeUserId), + ); options.trash = options.trash || false; + options.archived = userCanArchive && options.archived; + if (options.url != null && options.url.trim() !== "") { ciphers = await this.cipherService.getAllDecryptedForUrl(options.url, activeUserId); } else { @@ -85,9 +92,12 @@ export class ListCommand { options.organizationId != null ) { ciphers = ciphers.filter((c) => { - if (options.trash !== c.isDeleted) { + const matchesStateOptions = this.matchesStateOptions(c, options); + + if (!matchesStateOptions) { return false; } + if (options.folderId != null) { if (options.folderId === "notnull" && c.folderId != null) { return true; @@ -131,11 +141,16 @@ export class ListCommand { return false; }); } else if (options.search == null || options.search.trim() === "") { - ciphers = ciphers.filter((c) => options.trash === c.isDeleted); + ciphers = ciphers.filter((c) => this.matchesStateOptions(c, options)); } if (options.search != null && options.search.trim() !== "") { - ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash); + ciphers = this.searchService.searchCiphersBasic( + ciphers, + options.search, + options.trash, + options.archived, + ); } ciphers = await this.cliRestrictedItemTypesService.filterRestrictedCiphers(ciphers); @@ -287,6 +302,17 @@ export class ListCommand { const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o))); return Response.success(res); } + + /** + * Checks if the cipher passes either the trash or the archive options. + * @returns true if the cipher passes *any* of the filters + */ + private matchesStateOptions(c: CipherView, options: Options): boolean { + const passesTrashFilter = options.trash && c.isDeleted; + const passesArchivedFilter = options.archived && c.isArchived; + + return passesTrashFilter || passesArchivedFilter; + } } class Options { @@ -296,6 +322,7 @@ class Options { search: string; url: string; trash: boolean; + archived: boolean; constructor(passedOptions: Record) { this.organizationId = passedOptions?.organizationid || passedOptions?.organizationId; @@ -304,5 +331,6 @@ class Options { this.search = passedOptions?.search; this.url = passedOptions?.url; this.trash = CliUtils.convertBooleanOption(passedOptions?.trash); + this.archived = CliUtils.convertBooleanOption(passedOptions?.archived); } } diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 0b30193ffd4..d8cefdfce5d 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -2,8 +2,14 @@ import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { UserId } from "@bitwarden/user-core"; import { Response } from "../models/response"; @@ -12,6 +18,8 @@ export class RestoreCommand { private cipherService: CipherService, private accountService: AccountService, private cipherAuthorizationService: CipherAuthorizationService, + private cipherArchiveService: CipherArchiveService, + private configService: ConfigService, ) {} async run(object: string, id: string): Promise { @@ -30,10 +38,23 @@ export class RestoreCommand { private async restoreCipher(id: string) { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipher = await this.cipherService.get(id, activeUserId); + const isArchivedVaultEnabled = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.PM19148_InnovationArchive), + ); if (cipher == null) { return Response.notFound(); } + + if (cipher.archivedDate && isArchivedVaultEnabled) { + return this.restoreArchivedCipher(cipher, activeUserId); + } else { + return this.restoreDeletedCipher(cipher, activeUserId); + } + } + + /** Restores a cipher from the trash. */ + private async restoreDeletedCipher(cipher: Cipher, userId: UserId) { if (cipher.deletedDate == null) { return Response.badRequest("Cipher is not in trash."); } @@ -47,7 +68,17 @@ export class RestoreCommand { } try { - await this.cipherService.restoreWithServer(id, activeUserId); + await this.cipherService.restoreWithServer(cipher.id, userId); + return Response.success(); + } catch (e) { + return Response.error(e); + } + } + + /** Restore a cipher from the archive vault */ + private async restoreArchivedCipher(cipher: Cipher, userId: UserId) { + try { + await this.cipherArchiveService.unarchiveWithServer(cipher.id as CipherId, userId); return Response.success(); } catch (e) { return Response.error(e); diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index c0ec37d3c9c..5bf19333f35 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -51,7 +51,7 @@ export class ServeCommand { .use(koaBodyParser()) .use(koaJson({ pretty: false, param: "pretty" })); - this.serveConfigurator.configureRouter(router); + await this.serveConfigurator.configureRouter(router); server.use(router.routes()).use(router.allowedMethods()); diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 6ae2776eae7..3c80d12af2f 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -5,6 +5,8 @@ import * as koaRouter from "@koa/router"; import * as koa from "koa"; import { firstValueFrom, map } from "rxjs"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + import { ConfirmCommand } from "./admin-console/commands/confirm.command"; import { ShareCommand } from "./admin-console/commands/share.command"; import { LockCommand } from "./auth/commands/lock.command"; @@ -26,6 +28,7 @@ import { SendListCommand, SendRemovePasswordCommand, } from "./tools/send"; +import { ArchiveCommand } from "./vault/archive.command"; import { CreateCommand } from "./vault/create.command"; import { DeleteCommand } from "./vault/delete.command"; import { SyncCommand } from "./vault/sync.command"; @@ -40,6 +43,7 @@ export class OssServeConfigurator { private statusCommand: StatusCommand; private syncCommand: SyncCommand; private deleteCommand: DeleteCommand; + private archiveCommand: ArchiveCommand; private confirmCommand: ConfirmCommand; private restoreCommand: RestoreCommand; private lockCommand: LockCommand; @@ -81,6 +85,7 @@ export class OssServeConfigurator { this.serviceContainer.accountService, this.serviceContainer.keyService, this.serviceContainer.cliRestrictedItemTypesService, + this.serviceContainer.cipherArchiveService, ); this.createCommand = new CreateCommand( this.serviceContainer.cipherService, @@ -104,6 +109,7 @@ export class OssServeConfigurator { this.serviceContainer.accountService, this.serviceContainer.cliRestrictedItemTypesService, this.serviceContainer.policyService, + this.serviceContainer.billingAccountProfileStateService, ); this.generateCommand = new GenerateCommand( this.serviceContainer.passwordGenerationService, @@ -127,6 +133,13 @@ export class OssServeConfigurator { this.serviceContainer.accountService, this.serviceContainer.cliRestrictedItemTypesService, ); + this.archiveCommand = new ArchiveCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + this.serviceContainer.configService, + this.serviceContainer.cipherArchiveService, + this.serviceContainer.billingAccountProfileStateService, + ); this.confirmCommand = new ConfirmCommand( this.serviceContainer.apiService, this.serviceContainer.keyService, @@ -140,6 +153,8 @@ export class OssServeConfigurator { this.serviceContainer.cipherService, this.serviceContainer.accountService, this.serviceContainer.cipherAuthorizationService, + this.serviceContainer.cipherArchiveService, + this.serviceContainer.configService, ); this.shareCommand = new ShareCommand( this.serviceContainer.cipherService, @@ -199,7 +214,7 @@ export class OssServeConfigurator { ); } - configureRouter(router: koaRouter) { + async configureRouter(router: koaRouter) { router.get("/generate", async (ctx, next) => { const response = await this.generateCommand.run(ctx.request.query); this.processResponse(ctx.response, response); @@ -401,6 +416,23 @@ export class OssServeConfigurator { this.processResponse(ctx.response, response); await next(); }); + + const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag( + FeatureFlag.PM19148_InnovationArchive, + ); + + if (isArchivedEnabled) { + router.post("/archive/:object/:id", async (ctx, next) => { + if (await this.errorIfLocked(ctx.response)) { + await next(); + return; + } + let response: Response = null; + response = await this.archiveCommand.run(ctx.params.object, ctx.params.id); + this.processResponse(ctx.response, response); + await next(); + }); + } } protected processResponse(res: koa.Response, commandResponse: Response) { diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 4d541739aab..8f202bc0845 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -5,6 +5,7 @@ import { program, Command, OptionValues } from "commander"; import { firstValueFrom, of, switchMap } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { LockCommand } from "./auth/commands/lock.command"; import { LoginCommand } from "./auth/commands/login.command"; @@ -26,6 +27,10 @@ const writeLn = CliUtils.writeLn; export class Program extends BaseProgram { async register() { + const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag( + FeatureFlag.PM19148_InnovationArchive, + ); + program .option("--pretty", "Format output. JSON is tabbed with two spaces.") .option("--raw", "Return raw output instead of a descriptive message.") @@ -94,6 +99,9 @@ export class Program extends BaseProgram { " bw edit folder c7c7b60b-9c61-40f2-8ccd-36c49595ed72 eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg==", ); writeLn(" bw delete item 99ee88d2-6046-4ea7-92c2-acac464b1412"); + if (isArchivedEnabled) { + writeLn(" bw archive item 99ee88d2-6046-4ea7-92c2-acac464b1412"); + } writeLn(" bw generate -lusn --length 18"); writeLn(" bw config server https://bitwarden.example.com"); writeLn(" bw send -f ./file.ext"); diff --git a/apps/cli/src/register-oss-programs.ts b/apps/cli/src/register-oss-programs.ts index 1fc1f0119d2..71d7aaa0d52 100644 --- a/apps/cli/src/register-oss-programs.ts +++ b/apps/cli/src/register-oss-programs.ts @@ -15,7 +15,7 @@ export async function registerOssPrograms(serviceContainer: ServiceContainer) { await program.register(); const vaultProgram = new VaultProgram(serviceContainer); - vaultProgram.register(); + await vaultProgram.register(); const sendProgram = new SendProgram(serviceContainer); sendProgram.register(); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 7b148b2a3d5..8fb48fbc1ee 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -125,6 +125,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider"; import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { @@ -132,6 +133,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service"; import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; @@ -303,6 +305,7 @@ export class ServiceContainer { cipherEncryptionService: CipherEncryptionService; restrictedItemTypesService: RestrictedItemTypesService; cliRestrictedItemTypesService: CliRestrictedItemTypesService; + cipherArchiveService: CipherArchiveService; constructor() { let p = null; @@ -730,6 +733,13 @@ export class ServiceContainer { this.messagingService, ); + this.cipherArchiveService = new DefaultCipherArchiveService( + this.cipherService, + this.apiService, + this.billingAccountProfileStateService, + this.configService, + ); + this.folderService = new FolderService( this.keyService, this.encryptService, diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 5b35f6b0499..21f87feab00 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { program, Command } from "commander"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + import { ConfirmCommand } from "./admin-console/commands/confirm.command"; import { ShareCommand } from "./admin-console/commands/share.command"; import { BaseProgram } from "./base-program"; @@ -13,25 +15,34 @@ import { Response } from "./models/response"; import { ExportCommand } from "./tools/export.command"; import { ImportCommand } from "./tools/import.command"; import { CliUtils } from "./utils"; +import { ArchiveCommand } from "./vault/archive.command"; import { CreateCommand } from "./vault/create.command"; import { DeleteCommand } from "./vault/delete.command"; const writeLn = CliUtils.writeLn; export class VaultProgram extends BaseProgram { - register() { + async register() { + const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag( + FeatureFlag.PM19148_InnovationArchive, + ); + program - .addCommand(this.listCommand()) + .addCommand(this.listCommand(isArchivedEnabled)) .addCommand(this.getCommand()) .addCommand(this.createCommand()) .addCommand(this.editCommand()) .addCommand(this.deleteCommand()) - .addCommand(this.restoreCommand()) + .addCommand(this.restoreCommand(isArchivedEnabled)) .addCommand(this.shareCommand("move", false)) .addCommand(this.confirmCommand()) .addCommand(this.importCommand()) .addCommand(this.exportCommand()) .addCommand(this.shareCommand("share", true)); + + if (isArchivedEnabled) { + program.addCommand(this.archiveCommand()); + } } private validateObject(requestedObject: string, validObjects: string[]): boolean { @@ -42,7 +53,7 @@ export class VaultProgram extends BaseProgram { Response.badRequest( 'Unknown object "' + requestedObject + - '". Allowed objects are ' + + '". Allowed objects are: ' + validObjects.join(", ") + ".", ), @@ -51,7 +62,7 @@ export class VaultProgram extends BaseProgram { return success; } - private listCommand(): Command { + private listCommand(isArchivedEnabled: boolean): Command { const listObjects = [ "items", "folders", @@ -61,7 +72,7 @@ export class VaultProgram extends BaseProgram { "organizations", ]; - return new Command("list") + const command = new Command("list") .argument("", "Valid objects are: " + listObjects.join(", ")) .description("List an array of objects from the vault.") .option("--search ", "Perform a search on the listed objects.") @@ -94,6 +105,9 @@ export class VaultProgram extends BaseProgram { " bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2 --organizationid notnull", ); writeLn(" bw list items --trash"); + if (isArchivedEnabled) { + writeLn(" bw list items --archived"); + } writeLn(" bw list folders --search email"); writeLn(" bw list org-members --organizationid 60556c31-e649-4b5d-8daf-fc1c391a1bf2"); writeLn("", true); @@ -116,11 +130,18 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.accountService, this.serviceContainer.keyService, this.serviceContainer.cliRestrictedItemTypesService, + this.serviceContainer.cipherArchiveService, ); const response = await command.run(object, cmd); this.processResponse(response); }); + + if (isArchivedEnabled) { + command.option("--archived", "Filter items that are archived."); + } + + return command; } private getCommand(): Command { @@ -286,6 +307,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.accountService, this.serviceContainer.cliRestrictedItemTypesService, this.serviceContainer.policyService, + this.serviceContainer.billingAccountProfileStateService, ); const response = await command.run(object, id, encodedJson, cmd); this.processResponse(response); @@ -336,12 +358,41 @@ export class VaultProgram extends BaseProgram { }); } - private restoreCommand(): Command { + private archiveCommand(): Command { + const archiveObjects = ["item"]; + return new Command("archive") + .argument("", "Valid objects are: " + archiveObjects.join(", ")) + .argument("", "Object's globally unique `id`.") + .description("Archive an object from the vault.") + .on("--help", () => { + writeLn("\n Examples:"); + writeLn(""); + writeLn(" bw archive item 7063feab-4b10-472e-b64c-785e2b870b92"); + writeLn("", true); + }) + .action(async (object, id) => { + if (!this.validateObject(object, archiveObjects)) { + return; + } + + await this.exitIfLocked(); + const command = new ArchiveCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + this.serviceContainer.configService, + this.serviceContainer.cipherArchiveService, + this.serviceContainer.billingAccountProfileStateService, + ); + const response = await command.run(object, id); + this.processResponse(response); + }); + } + + private restoreCommand(isArchivedEnabled: boolean): Command { const restoreObjects = ["item"]; - return new Command("restore") + const command = new Command("restore") .argument("", "Valid objects are: " + restoreObjects.join(", ")) .argument("", "Object's globally unique `id`.") - .description("Restores an object from the trash.") .on("--help", () => { writeLn("\n Examples:"); writeLn(""); @@ -358,10 +409,20 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.cipherService, this.serviceContainer.accountService, this.serviceContainer.cipherAuthorizationService, + this.serviceContainer.cipherArchiveService, + this.serviceContainer.configService, ); const response = await command.run(object, id); this.processResponse(response); }); + + if (isArchivedEnabled) { + command.description("Restores an object from the trash or archive."); + } else { + command.description("Restores an object from the trash."); + } + + return command; } private shareCommand(commandName: string, deprecated: boolean): Command { diff --git a/apps/cli/src/vault/archive.command.ts b/apps/cli/src/vault/archive.command.ts new file mode 100644 index 00000000000..5ced2282c6d --- /dev/null +++ b/apps/cli/src/vault/archive.command.ts @@ -0,0 +1,109 @@ +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; +import { UserId } from "@bitwarden/user-core"; + +import { Response } from "../models/response"; + +export class ArchiveCommand { + constructor( + private cipherService: CipherService, + private accountService: AccountService, + private configService: ConfigService, + private cipherArchiveService: CipherArchiveService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + ) {} + + async run(object: string, id: string): Promise { + const featureFlagEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM19148_InnovationArchive, + ); + + if (!featureFlagEnabled) { + return Response.notFound(); + } + + if (id != null) { + id = id.toLowerCase(); + } + + const normalizedObject = object.toLowerCase(); + + if (normalizedObject === "item") { + return this.archiveCipher(id); + } + + return Response.badRequest("Unknown object."); + } + + private async archiveCipher(cipherId: string) { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const cipher = await this.cipherService.get(cipherId, activeUserId); + + if (cipher == null) { + return Response.notFound(); + } + + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); + + const { canArchive, errorMessage } = await this.userCanArchiveCipher(cipherView, activeUserId); + + if (!canArchive) { + return Response.error(errorMessage); + } + + try { + await this.cipherArchiveService.archiveWithServer(cipherView.id as CipherId, activeUserId); + return Response.success(); + } catch (e) { + return Response.error(e); + } + } + + /** + * Determines if the user can archive the given cipher. + * When the user cannot archive the cipher, an appropriate error message is provided. + */ + private async userCanArchiveCipher( + cipher: CipherView, + userId: UserId, + ): Promise< + { canArchive: true; errorMessage?: never } | { canArchive: false; errorMessage: string } + > { + const hasPremiumFromAnySource = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId), + ); + + switch (true) { + case !hasPremiumFromAnySource: { + return { + canArchive: false, + errorMessage: "Premium status is required to use this feature.", + }; + } + case CipherViewLikeUtils.isArchived(cipher): { + return { canArchive: false, errorMessage: "Item is already archived." }; + } + case CipherViewLikeUtils.isDeleted(cipher): { + return { + canArchive: false, + errorMessage: "Item is in the trash, the item must be restored before archiving.", + }; + } + case cipher.organizationId != null: { + return { canArchive: false, errorMessage: "Cannot archive items in an organization." }; + } + default: + return { canArchive: true }; + } + } +} diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index 1ab76c74655..3341a428970 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -9,11 +9,11 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { CipherArchiveService } from "@bitwarden/vault"; import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/individual-vault/vault-filter/components/vault-filter.component"; import { VaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts index 6feaa52d190..1fc3047f2a3 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts @@ -19,12 +19,12 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { CipherArchiveService } from "@bitwarden/vault"; import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services"; import { VaultFilterService } from "../services/abstractions/vault-filter.service"; diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 01082bfcd60..a1c44a9f623 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -54,6 +54,7 @@ import { uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.se import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -77,7 +78,6 @@ import { AttachmentDialogCloseResult, AttachmentDialogResult, AttachmentsV2Component, - CipherArchiveService, CipherFormConfig, CollectionAssignmentResult, DecryptionFailureDialogComponent, diff --git a/bitwarden_license/bit-cli/src/bit-serve-configurator.ts b/bitwarden_license/bit-cli/src/bit-serve-configurator.ts index c669eb70920..71df651d9d0 100644 --- a/bitwarden_license/bit-cli/src/bit-serve-configurator.ts +++ b/bitwarden_license/bit-cli/src/bit-serve-configurator.ts @@ -16,9 +16,9 @@ export class BitServeConfigurator extends OssServeConfigurator { super(serviceContainer); } - override configureRouter(router: koaRouter): void { + override async configureRouter(router: koaRouter): Promise { // Register OSS endpoints - super.configureRouter(router); + await super.configureRouter(router); // Register bit endpoints this.serveDeviceApprovals(router); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ff704394bc3..03d756ee11c 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -264,6 +264,7 @@ import { InternalSendService, SendService as SendServiceAbstraction, } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; @@ -284,6 +285,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service"; import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; @@ -296,7 +298,6 @@ import { DefaultTaskService, TaskService } from "@bitwarden/common/vault/tasks"; import { AnonLayoutWrapperDataService, DefaultAnonLayoutWrapperDataService, - DialogService, ToastService, } from "@bitwarden/components"; import { @@ -345,11 +346,7 @@ import { import { SafeInjectionToken } from "@bitwarden/ui-common"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports -import { - CipherArchiveService, - DefaultCipherArchiveService, - PasswordRepromptService, -} from "@bitwarden/vault"; +import { PasswordRepromptService } from "@bitwarden/vault"; import { IndividualVaultExportService, IndividualVaultExportServiceAbstraction, @@ -1652,8 +1649,6 @@ const safeProviders: SafeProvider[] = [ deps: [ CipherServiceAbstraction, ApiServiceAbstraction, - DialogService, - PasswordRepromptService, BillingAccountProfileStateService, ConfigService, ], diff --git a/libs/vault/src/abstractions/cipher-archive.service.ts b/libs/common/src/vault/abstractions/cipher-archive.service.ts similarity index 81% rename from libs/vault/src/abstractions/cipher-archive.service.ts rename to libs/common/src/vault/abstractions/cipher-archive.service.ts index 6240e4001c8..cb6c38ddf67 100644 --- a/libs/vault/src/abstractions/cipher-archive.service.ts +++ b/libs/common/src/vault/abstractions/cipher-archive.service.ts @@ -1,7 +1,6 @@ import { Observable } from "rxjs"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; export abstract class CipherArchiveService { @@ -10,5 +9,4 @@ export abstract class CipherArchiveService { abstract showArchiveVault$(userId: UserId): Observable; abstract archiveWithServer(ids: CipherId | CipherId[], userId: UserId): Promise; abstract unarchiveWithServer(ids: CipherId | CipherId[], userId: UserId): Promise; - abstract canInteract(cipher: CipherView): Promise; } diff --git a/libs/common/src/vault/abstractions/search.service.ts b/libs/common/src/vault/abstractions/search.service.ts index 6b01302613c..233dee9ec75 100644 --- a/libs/common/src/vault/abstractions/search.service.ts +++ b/libs/common/src/vault/abstractions/search.service.ts @@ -30,6 +30,7 @@ export abstract class SearchService { ciphers: C[], query: string, deleted?: boolean, + archived?: boolean, ): C[]; abstract searchSends(sends: SendView[], query: string): SendView[]; } diff --git a/libs/vault/src/services/default-cipher-archive.service.spec.ts b/libs/common/src/vault/services/default-cipher-archive.service.spec.ts similarity index 78% rename from libs/vault/src/services/default-cipher-archive.service.spec.ts rename to libs/common/src/vault/services/default-cipher-archive.service.spec.ts index ec2943ce7e4..972b04d2c4e 100644 --- a/libs/vault/src/services/default-cipher-archive.service.spec.ts +++ b/libs/common/src/vault/services/default-cipher-archive.service.spec.ts @@ -11,21 +11,14 @@ import { CipherBulkArchiveRequest, CipherBulkUnarchiveRequest, } from "@bitwarden/common/vault/models/request/cipher-bulk-archive.request"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { DialogService } from "@bitwarden/components"; import { CipherListView } from "@bitwarden/sdk-internal"; -import { DecryptionFailureDialogComponent } from "../components/decryption-failure-dialog/decryption-failure-dialog.component"; - import { DefaultCipherArchiveService } from "./default-cipher-archive.service"; -import { PasswordRepromptService } from "./password-reprompt.service"; describe("DefaultCipherArchiveService", () => { let service: DefaultCipherArchiveService; let mockCipherService: jest.Mocked; let mockApiService: jest.Mocked; - let mockDialogService: jest.Mocked; - let mockPasswordRepromptService: jest.Mocked; let mockBillingAccountProfileStateService: jest.Mocked; let mockConfigService: jest.Mocked; @@ -35,16 +28,12 @@ describe("DefaultCipherArchiveService", () => { beforeEach(() => { mockCipherService = mock(); mockApiService = mock(); - mockDialogService = mock(); - mockPasswordRepromptService = mock(); mockBillingAccountProfileStateService = mock(); mockConfigService = mock(); service = new DefaultCipherArchiveService( mockCipherService, mockApiService, - mockDialogService, - mockPasswordRepromptService, mockBillingAccountProfileStateService, mockConfigService, ); @@ -244,46 +233,4 @@ describe("DefaultCipherArchiveService", () => { ); }); }); - - describe("canInteract", () => { - let mockCipherView: CipherView; - - beforeEach(() => { - mockCipherView = { - id: cipherId, - decryptionFailure: false, - } as unknown as CipherView; - }); - - it("should return false and open dialog when cipher has decryption failure", async () => { - mockCipherView.decryptionFailure = true; - const openSpy = jest.spyOn(DecryptionFailureDialogComponent, "open").mockImplementation(); - - const result = await service.canInteract(mockCipherView); - - expect(result).toBe(false); - expect(openSpy).toHaveBeenCalledWith(mockDialogService, { - cipherIds: [cipherId], - }); - }); - - it("should return password reprompt result when no decryption failure", async () => { - mockPasswordRepromptService.passwordRepromptCheck.mockResolvedValue(true); - - const result = await service.canInteract(mockCipherView); - - expect(result).toBe(true); - expect(mockPasswordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith( - mockCipherView, - ); - }); - - it("should return false when password reprompt fails", async () => { - mockPasswordRepromptService.passwordRepromptCheck.mockResolvedValue(false); - - const result = await service.canInteract(mockCipherView); - - expect(result).toBe(false); - }); - }); }); diff --git a/libs/vault/src/services/default-cipher-archive.service.ts b/libs/common/src/vault/services/default-cipher-archive.service.ts similarity index 83% rename from libs/vault/src/services/default-cipher-archive.service.ts rename to libs/common/src/vault/services/default-cipher-archive.service.ts index d9a0ec54d73..5c627d687b2 100644 --- a/libs/vault/src/services/default-cipher-archive.service.ts +++ b/libs/common/src/vault/services/default-cipher-archive.service.ts @@ -12,27 +12,21 @@ import { CipherBulkUnarchiveRequest, } from "@bitwarden/common/vault/models/request/cipher-bulk-archive.request"; import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherViewLike, CipherViewLikeUtils, } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; -import { DialogService } from "@bitwarden/components"; import { CipherArchiveService } from "../abstractions/cipher-archive.service"; -import { DecryptionFailureDialogComponent } from "../components/decryption-failure-dialog/decryption-failure-dialog.component"; - -import { PasswordRepromptService } from "./password-reprompt.service"; export class DefaultCipherArchiveService implements CipherArchiveService { constructor( private cipherService: CipherService, private apiService: ApiService, - private dialogService: DialogService, - private passwordRepromptService: PasswordRepromptService, private billingAccountProfileStateService: BillingAccountProfileStateService, private configService: ConfigService, ) {} + /** * Observable that contains the list of ciphers that have been archived. */ @@ -125,21 +119,4 @@ export class DefaultCipherArchiveService implements CipherArchiveService { await this.cipherService.replace(currentCiphers, userId); } - - /** - * Check if the user is able to interact with the cipher - * (password re-prompt / decryption failure checks). - * @param cipher - * @private - */ - async canInteract(cipher: CipherView) { - if (cipher.decryptionFailure) { - DecryptionFailureDialogComponent.open(this.dialogService, { - cipherIds: [cipher.id as CipherId], - }); - return false; - } - - return await this.passwordRepromptService.passwordRepromptCheck(cipher); - } } diff --git a/libs/common/src/vault/services/search.service.ts b/libs/common/src/vault/services/search.service.ts index cbd89cf1ab1..80fddda42d5 100644 --- a/libs/common/src/vault/services/search.service.ts +++ b/libs/common/src/vault/services/search.service.ts @@ -296,12 +296,20 @@ export class SearchService implements SearchServiceAbstraction { return results; } - searchCiphersBasic(ciphers: C[], query: string, deleted = false) { + searchCiphersBasic( + ciphers: C[], + query: string, + deleted = false, + archived = false, + ) { query = SearchService.normalizeSearchQuery(query.trim().toLowerCase()); return ciphers.filter((c) => { if (deleted !== CipherViewLikeUtils.isDeleted(c)) { return false; } + if (archived !== CipherViewLikeUtils.isArchived(c)) { + return false; + } if (c.name != null && c.name.toLowerCase().indexOf(query) > -1) { return true; } diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index 5acac9ec009..efaefc77ade 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -27,5 +27,3 @@ export { SshImportPromptService } from "./services/ssh-import-prompt.service"; export * from "./abstractions/change-login-password.service"; export * from "./services/default-change-login-password.service"; -export * from "./abstractions/cipher-archive.service"; -export * from "./services/default-cipher-archive.service"; From d4f68e8bade77061612d17394d5495f84ae1627b Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:29:58 +0200 Subject: [PATCH 08/83] [PM-25473] Non-encryption passkeys prevent key rotation (#16514) * consistent webauthn filtering as in server by prfStatus, better docs * test coverage --- ...bauthn-login-credential-prf-status.enum.ts | 9 +++ .../webauthn-login-credential.response.ts | 2 +- .../webauthn-login-admin.service.spec.ts | 79 ++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts index 3073917e57b..02f870f094d 100644 --- a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts +++ b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts @@ -1,7 +1,16 @@ // FIXME: update to use a const object instead of a typescript enum // eslint-disable-next-line @bitwarden/platform/no-enums export enum WebauthnLoginCredentialPrfStatus { + /** + * Encrypted user key present, PRF function is supported. + */ Enabled = 0, + /** + * PRF function is supported. + */ Supported = 1, + /** + * PRF function is not supported. + */ Unsupported = 2, } diff --git a/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential.response.ts b/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential.response.ts index 85e7a7368e0..aba5940d752 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential.response.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential.response.ts @@ -40,6 +40,6 @@ export class WebauthnLoginCredentialResponse extends BaseResponse { } hasPrfKeyset(): boolean { - return this.encryptedUserKey != null && this.encryptedPublicKey != null; + return this.prfStatus === WebauthnLoginCredentialPrfStatus.Enabled; } } diff --git a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.spec.ts b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.spec.ts index c2a9946ea38..74323773e66 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.spec.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-admin.service.spec.ts @@ -10,15 +10,19 @@ import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/ab import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view"; import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { makeSymmetricCryptoKey } from "@bitwarden/common/spec"; +import { makeEncString, makeSymmetricCryptoKey } from "@bitwarden/common/spec"; import { PrfKey, UserKey } from "@bitwarden/common/types/key"; +import { UserId } from "@bitwarden/user-core"; +import { WebauthnLoginCredentialPrfStatus } from "../../enums/webauthn-login-credential-prf-status.enum"; import { CredentialCreateOptionsView } from "../../views/credential-create-options.view"; import { PendingWebauthnLoginCredentialView } from "../../views/pending-webauthn-login-credential.view"; import { RotateableKeySetService } from "../rotateable-key-set.service"; import { EnableCredentialEncryptionRequest } from "./request/enable-credential-encryption.request"; +import { WebauthnLoginCredentialResponse } from "./response/webauthn-login-credential.response"; import { WebAuthnLoginAdminApiService } from "./webauthn-login-admin-api.service"; import { WebauthnLoginAdminService } from "./webauthn-login-admin.service"; @@ -248,6 +252,79 @@ describe("WebauthnAdminService", () => { expect(rotateKeySetMock).not.toHaveBeenCalled(); }); }); + + describe("getRotatedData", () => { + const mockRotatedPublicKey = makeEncString("rotated_encryptedPublicKey"); + const mockRotatedUserKey = makeEncString("rotated_encryptedUserKey"); + const oldUserKey = makeSymmetricCryptoKey(64) as UserKey; + const newUserKey = makeSymmetricCryptoKey(64) as UserKey; + const userId = Utils.newGuid() as UserId; + + it("should only include credentials with PRF keysets", async () => { + const responseUnsupported = new WebauthnLoginCredentialResponse({ + id: "test-credential-id-1", + name: "Test Credential 1", + prfStatus: WebauthnLoginCredentialPrfStatus.Unsupported, + encryptedPublicKey: null, + encryptedUserKey: null, + }); + const responseSupported = new WebauthnLoginCredentialResponse({ + id: "test-credential-id-2", + name: "Test Credential 2", + prfStatus: WebauthnLoginCredentialPrfStatus.Supported, + encryptedPublicKey: null, + encryptedUserKey: null, + }); + const responseEnabled = new WebauthnLoginCredentialResponse({ + id: "test-credential-id-3", + name: "Test Credential 3", + prfStatus: WebauthnLoginCredentialPrfStatus.Enabled, + encryptedPublicKey: makeEncString("encryptedPublicKey").toJSON(), + encryptedUserKey: makeEncString("encryptedUserKey").toJSON(), + }); + + apiService.getCredentials.mockResolvedValue( + new ListResponse( + { + data: [responseUnsupported, responseSupported, responseEnabled], + }, + WebauthnLoginCredentialResponse, + ), + ); + + rotateableKeySetService.rotateKeySet.mockResolvedValue( + new RotateableKeySet(mockRotatedUserKey, mockRotatedPublicKey), + ); + + const result = await service.getRotatedData(oldUserKey, newUserKey, userId); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual( + expect.objectContaining({ + id: "test-credential-id-3", + encryptedPublicKey: mockRotatedPublicKey, + encryptedUserKey: mockRotatedUserKey, + }), + ); + expect(rotateableKeySetService.rotateKeySet).toHaveBeenCalledTimes(1); + expect(rotateableKeySetService.rotateKeySet).toHaveBeenCalledWith( + responseEnabled.getRotateableKeyset(), + oldUserKey, + newUserKey, + ); + }); + + it("should error when getCredentials fails", async () => { + const expectedError = "API connection failed"; + apiService.getCredentials.mockRejectedValue(new Error(expectedError)); + + await expect(service.getRotatedData(oldUserKey, newUserKey, userId)).rejects.toThrow( + expectedError, + ); + + expect(rotateableKeySetService.rotateKeySet).not.toHaveBeenCalled(); + }); + }); }); function createCredentialCreateOptions(): CredentialCreateOptionsView { From f793c2da09bf94cb2492c714ce5b75edc030fb9d Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Tue, 30 Sep 2025 11:33:39 -0400 Subject: [PATCH 09/83] remove feature flag (#16640) --- .../deprecated_vault.component.html | 121 -- .../collections/deprecated_vault.component.ts | 1389 ----------------- .../collections/vault-routing.module.ts | 21 +- .../collections/vault.component.ts | 2 +- .../organizations/collections/vault.module.ts | 4 +- libs/common/src/enums/feature-flag.enum.ts | 2 - 6 files changed, 9 insertions(+), 1530 deletions(-) delete mode 100644 apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html delete mode 100644 apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.ts diff --git a/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html b/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html deleted file mode 100644 index 326dc627e17..00000000000 --- a/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html +++ /dev/null @@ -1,121 +0,0 @@ -@if (organization) { - - - - -} - - - -
-
- -
-
- - - {{ "all" | i18n }} - - - - {{ "addAccess" | i18n }} - - - - {{ trashCleanupWarning }} - - - - - - {{ "noItemsInList" | i18n }} - - - - - -
- - {{ "loading" | i18n }} -
-
-
diff --git a/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.ts deleted file mode 100644 index fce2827c073..00000000000 --- a/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.ts +++ /dev/null @@ -1,1389 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { ActivatedRoute, Params, Router } from "@angular/router"; -import { - BehaviorSubject, - combineLatest, - firstValueFrom, - lastValueFrom, - merge, - Observable, - Subject, -} from "rxjs"; -import { - concatMap, - debounceTime, - distinctUntilChanged, - filter, - first, - map, - shareReplay, - switchMap, - takeUntil, - tap, -} from "rxjs/operators"; - -import { - CollectionAdminService, - CollectionAdminView, - CollectionService, - CollectionView, - Unassigned, -} from "@bitwarden/admin-console/common"; -import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { NoResults } from "@bitwarden/assets/svg"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; -import { EventType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; -import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; -import { - BannerModule, - DialogRef, - DialogService, - NoItemsModule, - ToastService, -} from "@bitwarden/components"; -import { - AttachmentDialogResult, - AttachmentsV2Component, - CipherFormConfig, - CipherFormConfigService, - CollectionAssignmentResult, - DecryptionFailureDialogComponent, - PasswordRepromptService, -} from "@bitwarden/vault"; -import { - OrganizationFreeTrialWarningComponent, - OrganizationResellerRenewalWarningComponent, -} from "@bitwarden/web-vault/app/billing/organizations/warnings/components"; -import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services"; -import { VaultItemsComponent } from "@bitwarden/web-vault/app/vault/components/vault-items/vault-items.component"; - -import { SharedModule } from "../../../shared"; -import { AssignCollectionsWebComponent } from "../../../vault/components/assign-collections"; -import { - VaultItemDialogComponent, - VaultItemDialogMode, - VaultItemDialogResult, -} from "../../../vault/components/vault-item-dialog/vault-item-dialog.component"; -import { VaultItemEvent } from "../../../vault/components/vault-items/vault-item-event"; -import { VaultItemsModule } from "../../../vault/components/vault-items/vault-items.module"; -import { - BulkDeleteDialogResult, - openBulkDeleteDialog, -} from "../../../vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component"; -import { VaultFilterService } from "../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; -import { RoutedVaultFilterBridgeService } from "../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; -import { RoutedVaultFilterService } from "../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; -import { createFilterFunction } from "../../../vault/individual-vault/vault-filter/shared/models/filter-function"; -import { - All, - RoutedVaultFilterModel, -} from "../../../vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model"; -import { VaultFilter } from "../../../vault/individual-vault/vault-filter/shared/models/vault-filter.model"; -import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; -import { GroupApiService, GroupView } from "../core"; -import { openEntityEventsDialog } from "../manage/entity-events.component"; -import { - CollectionDialogAction, - CollectionDialogTabType, - openCollectionDialog, -} from "../shared/components/collection-dialog"; - -import { - BulkCollectionsDialogComponent, - BulkCollectionsDialogResult, -} from "./bulk-collections-dialog"; -import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component"; -import { getFlatCollectionTree, getNestedCollectionTree } from "./utils"; -import { VaultFilterModule } from "./vault-filter/vault-filter.module"; -import { VaultHeaderComponent } from "./vault-header/vault-header.component"; - -const BroadcasterSubscriptionId = "OrgVaultComponent"; -const SearchTextDebounceInterval = 200; - -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -enum AddAccessStatusType { - All = 0, - AddAccess = 1, -} - -@Component({ - selector: "app-org-vault", - templateUrl: "deprecated_vault.component.html", - imports: [ - VaultHeaderComponent, - CollectionAccessRestrictedComponent, - VaultFilterModule, - VaultItemsModule, - SharedModule, - BannerModule, - NoItemsModule, - OrganizationFreeTrialWarningComponent, - OrganizationResellerRenewalWarningComponent, - ], - providers: [ - RoutedVaultFilterService, - RoutedVaultFilterBridgeService, - { provide: CipherFormConfigService, useClass: AdminConsoleCipherFormConfigService }, - ], -}) -export class VaultComponent implements OnInit, OnDestroy { - protected Unassigned = Unassigned; - - trashCleanupWarning: string = null; - activeFilter: VaultFilter = new VaultFilter(); - - protected showAddAccessToggle = false; - protected noItemIcon = NoResults; - protected performingInitialLoad = true; - protected refreshing = false; - protected processingEvent = false; - protected filter: RoutedVaultFilterModel = {}; - protected organization: Organization; - protected allCollections: CollectionAdminView[]; - protected allGroups: GroupView[]; - protected ciphers: CipherView[]; - protected collections: CollectionAdminView[]; - protected selectedCollection: TreeNode | undefined; - protected isEmpty: boolean; - protected showCollectionAccessRestricted: boolean; - protected currentSearchText$: Observable; - protected prevCipherId: string | null = null; - protected userId: UserId; - /** - * A list of collections that the user can assign items to and edit those items within. - * @protected - */ - protected editableCollections$: Observable; - protected allCollectionsWithoutUnassigned$: Observable; - - protected get hideVaultFilters(): boolean { - return this.organization?.isProviderUser && !this.organization?.isMember; - } - - private searchText$ = new Subject(); - private refresh$ = new BehaviorSubject(null); - private destroy$ = new Subject(); - protected addAccessStatus$ = new BehaviorSubject(0); - private vaultItemDialogRef?: DialogRef | undefined; - - @ViewChild("vaultItems", { static: false }) vaultItemsComponent: VaultItemsComponent; - - constructor( - private route: ActivatedRoute, - private organizationService: OrganizationService, - protected vaultFilterService: VaultFilterService, - private routedVaultFilterBridgeService: RoutedVaultFilterBridgeService, - private routedVaultFilterService: RoutedVaultFilterService, - private router: Router, - private changeDetectorRef: ChangeDetectorRef, - private syncService: SyncService, - private i18nService: I18nService, - private dialogService: DialogService, - private messagingService: MessagingService, - private broadcasterService: BroadcasterService, - private ngZone: NgZone, - private platformUtilsService: PlatformUtilsService, - private cipherService: CipherService, - private passwordRepromptService: PasswordRepromptService, - private collectionAdminService: CollectionAdminService, - private searchService: SearchService, - private searchPipe: SearchPipe, - private groupService: GroupApiService, - private logService: LogService, - private eventCollectionService: EventCollectionService, - private totpService: TotpService, - private apiService: ApiService, - private toastService: ToastService, - private configService: ConfigService, - private cipherFormConfigService: CipherFormConfigService, - protected billingApiService: BillingApiServiceAbstraction, - private accountService: AccountService, - private organizationWarningsService: OrganizationWarningsService, - private collectionService: CollectionService, - ) {} - - async ngOnInit() { - this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - - this.trashCleanupWarning = this.i18nService.t( - this.platformUtilsService.isSelfHost() - ? "trashCleanupWarningSelfHosted" - : "trashCleanupWarning", - ); - - const filter$ = this.routedVaultFilterService.filter$; - - // FIXME: The RoutedVaultFilterModel uses `organizationId: Unassigned` to represent the individual vault, - // but that is never used in Admin Console. This function narrows the type so it doesn't pollute our code here, - // but really we should change to using our own vault filter model that only represents valid states in AC. - const isOrganizationId = (value: OrganizationId | Unassigned): value is OrganizationId => - value !== Unassigned; - const organizationId$ = filter$.pipe( - map((filter) => filter.organizationId), - filter((filter) => filter !== undefined), - filter(isOrganizationId), - distinctUntilChanged(), - ); - - const organization$ = this.accountService.activeAccount$.pipe( - map((account) => account?.id), - switchMap((id) => - organizationId$.pipe( - switchMap((organizationId) => - this.organizationService - .organizations$(id) - .pipe(map((organizations) => organizations.find((org) => org.id === organizationId))), - ), - takeUntil(this.destroy$), - shareReplay({ refCount: false, bufferSize: 1 }), - ), - ), - ); - - const firstSetup$ = combineLatest([organization$, this.route.queryParams]).pipe( - first(), - switchMap(async ([organization]) => { - this.organization = organization; - - if (!organization.canEditAnyCollection) { - await this.syncService.fullSync(false); - } - - return undefined; - }), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (message.successfully) { - this.refresh(); - this.changeDetectorRef.detectChanges(); - } - break; - } - }); - }); - - this.routedVaultFilterBridgeService.activeFilter$ - .pipe(takeUntil(this.destroy$)) - .subscribe((activeFilter) => { - this.activeFilter = activeFilter; - - // watch the active filters. Only show toggle when viewing the collections filter - if (!this.activeFilter.collectionId) { - this.showAddAccessToggle = false; - } - }); - - this.searchText$ - .pipe(debounceTime(SearchTextDebounceInterval), takeUntil(this.destroy$)) - .subscribe((searchText) => - this.router.navigate([], { - queryParams: { search: Utils.isNullOrEmpty(searchText) ? null : searchText }, - queryParamsHandling: "merge", - replaceUrl: true, - }), - ); - - this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search)); - - this.allCollectionsWithoutUnassigned$ = this.refresh$.pipe( - switchMap(() => organizationId$), - switchMap((orgId) => - this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.collectionAdminService.collectionAdminViews$(orgId, userId)), - ), - ), - shareReplay({ refCount: false, bufferSize: 1 }), - ); - - this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe( - map((collections) => { - // Users that can edit all ciphers can implicitly add to / edit within any collection - if (this.organization.canEditAllCiphers) { - return collections; - } - return collections.filter((c) => c.assigned); - }), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - const allCollections$ = combineLatest([ - organizationId$, - this.allCollectionsWithoutUnassigned$, - ]).pipe( - map(([organizationId, allCollections]) => { - // FIXME: We should not assert that the Unassigned type is a CollectionId. - // Instead we should consider representing the Unassigned collection as a different object, given that - // it is not actually a collection. - return allCollections.concat( - new CollectionAdminView({ - name: this.i18nService.t("unassigned"), - id: Unassigned as CollectionId, - organizationId, - }), - ); - }), - ); - - const allGroups$ = organizationId$.pipe( - switchMap((organizationId) => this.groupService.getAll(organizationId)), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - const allCiphers$ = combineLatest([organization$, this.refresh$]).pipe( - switchMap(async ([organization]) => { - // If user swaps organization reset the addAccessToggle - if (!this.showAddAccessToggle || organization) { - this.addAccessToggle(0); - } - let ciphers; - - // Restricted providers (who are not members) do not have access org cipher endpoint below - // Return early to avoid 404 response - if (!organization.isMember && organization.isProviderUser) { - return []; - } - - // If the user can edit all ciphers for the organization then fetch them ALL. - if (organization.canEditAllCiphers) { - ciphers = await this.cipherService.getAllFromApiForOrganization(organization.id); - ciphers?.forEach((c) => (c.edit = true)); - } else { - // Otherwise, only fetch ciphers they have access to (includes unassigned for admins). - ciphers = await this.cipherService.getManyFromApiForOrganization(organization.id); - } - - await this.searchService.indexCiphers(this.userId, ciphers, organization.id); - return ciphers; - }), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - const allCipherMap$ = allCiphers$.pipe( - map((ciphers) => { - return Object.fromEntries(ciphers.map((c) => [c.id, c])); - }), - ); - - const nestedCollections$ = allCollections$.pipe( - map((collections) => getNestedCollectionTree(collections)), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - const collections$ = combineLatest([ - nestedCollections$, - filter$, - this.currentSearchText$, - this.addAccessStatus$, - ]).pipe( - filter(([collections, filter]) => collections != undefined && filter != undefined), - concatMap(async ([collections, filter, searchText, addAccessStatus]) => { - if ( - filter.collectionId === Unassigned || - (filter.collectionId === undefined && filter.type !== undefined) - ) { - return []; - } - - this.showAddAccessToggle = false; - let searchableCollectionNodes: TreeNode[] = []; - if (filter.collectionId === undefined || filter.collectionId === All) { - searchableCollectionNodes = collections; - } else { - const selectedCollection = ServiceUtils.getTreeNodeObjectFromList( - collections, - filter.collectionId, - ); - searchableCollectionNodes = selectedCollection?.children ?? []; - } - - let collectionsToReturn: CollectionAdminView[] = []; - - if (await this.searchService.isSearchable(this.userId, searchText)) { - // Flatten the tree for searching through all levels - const flatCollectionTree: CollectionAdminView[] = - getFlatCollectionTree(searchableCollectionNodes); - - collectionsToReturn = this.searchPipe.transform( - flatCollectionTree, - searchText, - (collection) => collection.name, - (collection) => collection.id, - ); - } else { - collectionsToReturn = searchableCollectionNodes.map( - (treeNode: TreeNode): CollectionAdminView => treeNode.node, - ); - } - - // Add access toggle is only shown if allowAdminAccessToAllCollectionItems is false and there are unmanaged collections the user can edit - this.showAddAccessToggle = - !this.organization.allowAdminAccessToAllCollectionItems && - this.organization.canEditUnmanagedCollections && - collectionsToReturn.some((c) => c.unmanaged); - - if (addAccessStatus === 1 && this.showAddAccessToggle) { - collectionsToReturn = collectionsToReturn.filter((c) => c.unmanaged); - } - return collectionsToReturn; - }), - takeUntil(this.destroy$), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - const selectedCollection$ = combineLatest([nestedCollections$, filter$]).pipe( - filter(([collections, filter]) => collections != undefined && filter != undefined), - map(([collections, filter]) => { - if ( - filter.collectionId === undefined || - filter.collectionId === All || - filter.collectionId === Unassigned - ) { - return undefined; - } - - return ServiceUtils.getTreeNodeObjectFromList(collections, filter.collectionId); - }), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - const showCollectionAccessRestricted$ = combineLatest([ - filter$, - selectedCollection$, - organization$, - ]).pipe( - map(([filter, collection, organization]) => { - return ( - (filter.collectionId === Unassigned && !organization.canEditUnassignedCiphers) || - (!organization.canEditAllCiphers && collection != undefined && !collection.node.assigned) - ); - }), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - const ciphers$ = combineLatest([ - allCiphers$, - filter$, - this.currentSearchText$, - showCollectionAccessRestricted$, - ]).pipe( - filter(([ciphers, filter]) => ciphers != undefined && filter != undefined), - concatMap(async ([ciphers, filter, searchText, showCollectionAccessRestricted]) => { - if (filter.collectionId === undefined && filter.type === undefined) { - return []; - } - - if (showCollectionAccessRestricted) { - // Do not show ciphers for restricted collections - // Ciphers belonging to multiple collections may still be present in $allCiphers and shouldn't be visible - return []; - } - - const filterFunction = createFilterFunction(filter); - - if (await this.searchService.isSearchable(this.userId, searchText)) { - return await this.searchService.searchCiphers( - this.userId, - searchText, - [filterFunction], - ciphers, - ); - } - - return ciphers.filter(filterFunction); - }), - shareReplay({ refCount: true, bufferSize: 1 }), - ); - - firstSetup$ - .pipe( - switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])), - filter(() => this.vaultItemDialogRef == undefined), - switchMap(async ([qParams, allCiphersMap]) => { - const cipherId = getCipherIdFromParams(qParams); - - if (!cipherId) { - this.prevCipherId = null; - return; - } - - if (cipherId === this.prevCipherId) { - return; - } - - this.prevCipherId = cipherId; - - const cipher = allCiphersMap[cipherId]; - if (cipher) { - let action = qParams.action; - - if (action == "showFailedToDecrypt") { - DecryptionFailureDialogComponent.open(this.dialogService, { - cipherIds: [cipherId as CipherId], - }); - await this.router.navigate([], { - queryParams: { itemId: null, cipherId: null, action: null }, - queryParamsHandling: "merge", - replaceUrl: true, - }); - return; - } - - // Default to "view" - if (action == null) { - action = "view"; - } - - if (action === "view") { - await this.viewCipherById(cipher); - } else { - await this.editCipher(cipher, false); - } - } else { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("unknownCipher"), - }); - await this.router.navigate([], { - queryParams: { cipherId: null, itemId: null }, - queryParamsHandling: "merge", - }); - } - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - firstSetup$ - .pipe( - switchMap(() => combineLatest([this.route.queryParams, organization$, allCiphers$])), - switchMap(async ([qParams, organization, allCiphers$]) => { - const cipherId = qParams.viewEvents; - if (!cipherId) { - return; - } - const cipher = allCiphers$.find((c) => c.id === cipherId); - if (organization.useEvents && cipher != undefined) { - await this.viewEvents(cipher); - } else { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("unknownCipher"), - }); - await this.router.navigate([], { - queryParams: { viewEvents: null }, - queryParamsHandling: "merge", - }); - } - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - // Billing Warnings - organization$ - .pipe( - switchMap((organization) => - merge( - this.organizationWarningsService.showInactiveSubscriptionDialog$(organization), - this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization), - ), - ), - takeUntil(this.destroy$), - ) - .subscribe(); - // End Billing Warnings - - firstSetup$ - .pipe( - switchMap(() => this.refresh$), - tap(() => (this.refreshing = true)), - switchMap(() => - combineLatest([ - organization$, - filter$, - allCollections$, - allGroups$, - ciphers$, - collections$, - selectedCollection$, - showCollectionAccessRestricted$, - ]), - ), - takeUntil(this.destroy$), - ) - .subscribe( - ([ - organization, - filter, - allCollections, - allGroups, - ciphers, - collections, - selectedCollection, - showCollectionAccessRestricted, - ]) => { - this.organization = organization; - this.filter = filter; - this.allCollections = allCollections; - this.allGroups = allGroups; - this.ciphers = ciphers; - this.collections = collections; - this.selectedCollection = selectedCollection; - this.showCollectionAccessRestricted = showCollectionAccessRestricted; - - this.isEmpty = collections?.length === 0 && ciphers?.length === 0; - - // This is a temporary fix to avoid double fetching collections. - // TODO: Remove when implementing new VVR menu - this.vaultFilterService.reloadCollections(allCollections); - - this.refreshing = false; - this.performingInitialLoad = false; - }, - ); - } - - async navigateToPaymentMethod() { - const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, - ); - const route = managePaymentDetailsOutsideCheckout ? "payment-details" : "payment-method"; - await this.router.navigate(["organizations", `${this.organization?.id}`, "billing", route], { - state: { launchPaymentModalAutomatically: true }, - }); - } - - addAccessToggle(e: AddAccessStatusType) { - this.addAccessStatus$.next(e); - } - - get loading() { - return this.refreshing || this.processingEvent; - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - this.destroy$.next(); - this.destroy$.complete(); - } - - async onVaultItemsEvent(event: VaultItemEvent) { - this.processingEvent = true; - - try { - switch (event.type) { - case "viewAttachments": - await this.editCipherAttachments(event.item); - break; - case "clone": - await this.cloneCipher(event.item); - break; - case "restore": - if (event.items.length === 1) { - await this.restore(event.items[0]); - } else { - await this.bulkRestore(event.items); - } - break; - case "delete": { - const ciphers = event.items - .filter((i) => i.collection === undefined) - .map((i) => i.cipher); - const collections = event.items - .filter((i) => i.cipher === undefined) - .map((i) => i.collection); - if (ciphers.length === 1 && collections.length === 0) { - await this.deleteCipher(ciphers[0]); - } else if (ciphers.length === 0 && collections.length === 1) { - await this.deleteCollection(collections[0] as CollectionAdminView); - } else { - await this.bulkDelete(ciphers, collections, this.organization); - } - break; - } - case "copyField": - await this.copy(event.item, event.field); - break; - case "editCollection": - await this.editCollection( - event.item as CollectionAdminView, - CollectionDialogTabType.Info, - event.readonly, - ); - break; - case "viewCollectionAccess": - await this.editCollection( - event.item as CollectionAdminView, - CollectionDialogTabType.Access, - event.readonly, - ); - break; - case "bulkEditCollectionAccess": - await this.bulkEditCollectionAccess(event.items, this.organization); - break; - case "assignToCollections": - await this.bulkAssignToCollections(event.items); - break; - case "viewEvents": - await this.viewEvents(event.item); - break; - } - } finally { - this.processingEvent = false; - } - } - - filterSearchText(searchText: string) { - this.searchText$.next(searchText); - } - - async editCipherAttachments(cipher: CipherView) { - if (cipher?.reprompt !== 0 && !(await this.passwordRepromptService.showPasswordPrompt())) { - this.go({ cipherId: null, itemId: null }); - return; - } - - if (this.organization.maxStorageGb == null || this.organization.maxStorageGb === 0) { - this.messagingService.send("upgradeOrganization", { organizationId: cipher.organizationId }); - return; - } - - const dialogRef = AttachmentsV2Component.open(this.dialogService, { - cipherId: cipher.id as CipherId, - organizationId: cipher.organizationId as OrganizationId, - admin: true, - }); - - const result = await firstValueFrom(dialogRef.closed); - - if ( - result.action === AttachmentDialogResult.Removed || - result.action === AttachmentDialogResult.Uploaded - ) { - this.refresh(); - } - } - - /** Opens the Add/Edit Dialog */ - async addCipher(cipherType?: CipherType) { - const cipherFormConfig = await this.cipherFormConfigService.buildConfig( - "add", - null, - cipherType, - ); - - const collectionId: CollectionId | undefined = this.activeFilter.collectionId as CollectionId; - - cipherFormConfig.initialValues = { - organizationId: this.organization.id as OrganizationId, - collectionIds: collectionId ? [collectionId] : [], - }; - - await this.openVaultItemDialog("form", cipherFormConfig); - } - - /** - * Edit the given cipher or add a new cipher - * @param cipherView - When set, the cipher to be edited - * @param cloneCipher - `true` when the cipher should be cloned. - */ - async editCipher(cipher: CipherView | null, cloneCipher: boolean) { - if ( - cipher && - cipher.reprompt !== 0 && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - // didn't pass password prompt, so don't open add / edit modal - this.go({ cipherId: null, itemId: null }); - return; - } - - const cipherFormConfig = await this.cipherFormConfigService.buildConfig( - cloneCipher ? "clone" : "edit", - cipher?.id as CipherId | null, - ); - - await this.openVaultItemDialog("form", cipherFormConfig, cipher); - } - - /** Opens the view dialog for the given cipher unless password reprompt fails */ - async viewCipherById(cipher: CipherView) { - if (!cipher) { - return; - } - - if ( - cipher && - cipher.reprompt !== 0 && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - // Didn't pass password prompt, so don't open add / edit modal. - await this.go({ cipherId: null, itemId: null, action: null }); - return; - } - - const cipherFormConfig = await this.cipherFormConfigService.buildConfig( - "edit", - cipher.id as CipherId, - cipher.type, - ); - - await this.openVaultItemDialog( - "view", - cipherFormConfig, - cipher, - this.activeFilter.collectionId as CollectionId, - ); - } - - /** - * Open the combined view / edit dialog for a cipher. - */ - async openVaultItemDialog( - mode: VaultItemDialogMode, - formConfig: CipherFormConfig, - cipher?: CipherView, - activeCollectionId?: CollectionId, - ) { - const disableForm = cipher ? !cipher.edit && !this.organization.canEditAllCiphers : false; - // If the form is disabled, force the mode into `view` - const dialogMode = disableForm ? "view" : mode; - this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, { - mode: dialogMode, - formConfig, - disableForm, - activeCollectionId, - isAdminConsoleAction: true, - restore: this.restore, - }); - - const result = await lastValueFrom(this.vaultItemDialogRef.closed); - this.vaultItemDialogRef = undefined; - - // If the dialog was closed by deleting the cipher, refresh the vault. - if (result === VaultItemDialogResult.Deleted || result === VaultItemDialogResult.Saved) { - this.refresh(); - } - - // Clear the query params when the dialog closes - await this.go({ cipherId: null, itemId: null, action: null }); - } - - async cloneCipher(cipher: CipherView) { - if (cipher.login?.hasFido2Credentials) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "passkeyNotCopied" }, - content: { key: "passkeyNotCopiedAlert" }, - type: "info", - }); - - if (!confirmed) { - return false; - } - } - - await this.editCipher(cipher, true); - } - - restore = async (c: CipherView): Promise => { - if (!c.isDeleted) { - return; - } - - if ( - !this.organization.permissions.editAnyCollection && - !c.edit && - !this.organization.allowAdminAccessToAllCollectionItems - ) { - this.showMissingPermissionsError(); - return; - } - - if (!(await this.repromptCipher([c]))) { - return; - } - - // Allow restore of an Unassigned Item - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const asAdmin = this.organization?.canEditAnyCollection || c.isUnassigned; - await this.cipherService.restoreWithServer(c.id, activeUserId, asAdmin); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("restoredItem"), - }); - this.refresh(); - } catch (e) { - this.logService.error(e); - } - }; - - async bulkRestore(ciphers: CipherView[]) { - if ( - !this.organization.permissions.editAnyCollection && - ciphers.some((c) => !c.edit && !this.organization.allowAdminAccessToAllCollectionItems) - ) { - this.showMissingPermissionsError(); - return; - } - - if (!(await this.repromptCipher(ciphers))) { - return; - } - - // assess if there are unassigned ciphers and/or editable ciphers selected in bulk for restore - const editAccessCiphers: string[] = []; - const unassignedCiphers: string[] = []; - - // If user has edit all Access no need to check for unassigned ciphers - if (this.organization.canEditAllCiphers) { - ciphers.map((cipher) => { - editAccessCiphers.push(cipher.id); - }); - } else { - ciphers.map((cipher) => { - if (cipher.collectionIds.length === 0) { - unassignedCiphers.push(cipher.id); - } else if (cipher.edit) { - editAccessCiphers.push(cipher.id); - } - }); - } - - if (unassignedCiphers.length === 0 && editAccessCiphers.length === 0) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("nothingSelected"), - }); - return; - } - - if (unassignedCiphers.length > 0 || editAccessCiphers.length > 0) { - await this.cipherService.restoreManyWithServer( - [...unassignedCiphers, ...editAccessCiphers], - this.userId, - this.organization.id, - ); - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("restoredItems"), - }); - this.refresh(); - } - - async deleteCipher(c: CipherView): Promise { - if (!c.edit && !this.organization.canEditAllCiphers) { - this.showMissingPermissionsError(); - return; - } - - if (!(await this.repromptCipher([c]))) { - return; - } - - const permanent = c.isDeleted; - - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: permanent ? "permanentlyDeleteItem" : "deleteItem" }, - content: { key: permanent ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return false; - } - - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.deleteCipherWithServer(c.id, activeUserId, permanent, c.isUnassigned); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"), - }); - this.refresh(); - } catch (e) { - this.logService.error(e); - } - } - - async deleteCollection(collection: CollectionAdminView): Promise { - if (!collection.canDelete(this.organization)) { - this.showMissingPermissionsError(); - return; - } - const confirmed = await this.dialogService.openSimpleDialog({ - title: collection.name, - content: { key: "deleteCollectionConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - try { - await this.apiService.deleteCollection(this.organization?.id, collection.id); - await this.collectionService.delete([collection.id as CollectionId], this.userId); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("deletedCollectionId", collection.name), - }); - - // Clear the cipher cache to clear the deleted collection from the cipher state - await this.cipherService.clear(); - - // Navigate away if we deleted the collection we were viewing - if (this.selectedCollection?.node.id === collection.id) { - void this.router.navigate([], { - queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, - queryParamsHandling: "merge", - replaceUrl: true, - }); - } - - this.refresh(); - } catch (e) { - this.logService.error(e); - } - } - - async bulkDelete( - ciphers: CipherView[], - collections: CollectionView[], - organization: Organization, - ) { - if (!(await this.repromptCipher(ciphers))) { - return; - } - - // Allow bulk deleting of Unassigned Items - const unassignedCiphers: string[] = []; - const assignedCiphers: string[] = []; - - ciphers.map((c) => { - if (c.isUnassigned) { - unassignedCiphers.push(c.id); - } else { - assignedCiphers.push(c.id); - } - }); - - if (ciphers.length === 0 && collections.length === 0) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("nothingSelected"), - }); - return; - } - - const canDeleteCollections = - collections == null || collections.every((c) => c.canDelete(organization)); - const canDeleteCiphers = - ciphers == null || ciphers.every((c) => c.edit) || this.organization.canEditAllCiphers; - - if (!canDeleteCiphers || !canDeleteCollections) { - this.showMissingPermissionsError(); - return; - } - - const dialog = openBulkDeleteDialog(this.dialogService, { - data: { - permanent: this.filter.type === "trash", - cipherIds: assignedCiphers, - collections: collections, - organization, - unassignedCiphers, - }, - }); - - const result = await lastValueFrom(dialog.closed); - if (result === BulkDeleteDialogResult.Deleted) { - this.refresh(); - } - } - - async copy(cipher: CipherView, field: "username" | "password" | "totp") { - let aType; - let value; - let typeI18nKey; - - if (field === "username") { - aType = "Username"; - value = cipher.login.username; - typeI18nKey = "username"; - } else if (field === "password") { - aType = "Password"; - value = cipher.login.password; - typeI18nKey = "password"; - } else if (field === "totp") { - aType = "TOTP"; - const totpResponse = await firstValueFrom(this.totpService.getCode$(cipher.login.totp)); - value = totpResponse?.code; - typeI18nKey = "verificationCodeTotp"; - } else { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("unexpectedError"), - }); - return; - } - - if ( - this.passwordRepromptService.protectedFields().includes(aType) && - !(await this.repromptCipher([cipher])) - ) { - return; - } - - if (!cipher.viewPassword) { - return; - } - - this.platformUtilsService.copyToClipboard(value, { window: window }); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - }); - - if (field === "password") { - await this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); - } else if (field === "totp") { - await this.eventCollectionService.collect( - EventType.Cipher_ClientCopiedHiddenField, - cipher.id, - ); - } - } - - async addCollection(): Promise { - const dialog = openCollectionDialog(this.dialogService, { - data: { - organizationId: this.organization?.id, - parentCollectionId: this.selectedCollection?.node.id, - limitNestedCollections: !this.organization.canEditAnyCollection, - isAdminConsoleActive: true, - }, - }); - - const result = await lastValueFrom(dialog.closed); - if ( - result.action === CollectionDialogAction.Saved || - result.action === CollectionDialogAction.Deleted - ) { - this.refresh(); - } - } - - async editCollection( - c: CollectionAdminView, - tab: CollectionDialogTabType, - readonly: boolean, - ): Promise { - const dialog = openCollectionDialog(this.dialogService, { - data: { - collectionId: c?.id, - organizationId: this.organization?.id, - initialTab: tab, - readonly: readonly, - isAddAccessCollection: c.unmanaged, - limitNestedCollections: !this.organization.canEditAnyCollection, - isAdminConsoleActive: true, - }, - }); - - const result = await lastValueFrom(dialog.closed); - if ( - result.action === CollectionDialogAction.Saved || - result.action === CollectionDialogAction.Deleted - ) { - this.refresh(); - - // If we deleted the selected collection, navigate up/away - if ( - result.action === CollectionDialogAction.Deleted && - this.selectedCollection?.node.id === c?.id - ) { - void this.router.navigate([], { - queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, - queryParamsHandling: "merge", - replaceUrl: true, - }); - } - } - } - - async bulkEditCollectionAccess( - collections: CollectionView[], - organization: Organization, - ): Promise { - if (collections.length === 0) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("noCollectionsSelected"), - }); - return; - } - - if (collections.some((c) => !c.canEdit(organization))) { - this.showMissingPermissionsError(); - return; - } - - const dialog = BulkCollectionsDialogComponent.open(this.dialogService, { - data: { - collections, - organizationId: this.organization?.id, - }, - }); - - const result = await lastValueFrom(dialog.closed); - if (result === BulkCollectionsDialogResult.Saved) { - this.refresh(); - } - } - - async bulkAssignToCollections(items: CipherView[]) { - if (items.length === 0) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("nothingSelected"), - }); - return; - } - - const availableCollections = await firstValueFrom(this.editableCollections$); - - const dialog = AssignCollectionsWebComponent.open(this.dialogService, { - data: { - ciphers: items, - organizationId: this.organization?.id as OrganizationId, - availableCollections, - activeCollection: this.activeFilter?.selectedCollectionNode?.node, - isSingleCipherAdmin: - items.length === 1 && (this.organization?.canEditAllCiphers || items[0].isUnassigned), - }, - }); - - const result = await lastValueFrom(dialog.closed); - if (result === CollectionAssignmentResult.Saved) { - this.refresh(); - } - } - - async viewEvents(cipher: CipherView) { - await openEntityEventsDialog(this.dialogService, { - data: { - name: cipher.name, - organizationId: this.organization.id, - entityId: cipher.id, - showUser: true, - entity: "cipher", - }, - }); - } - - protected deleteCipherWithServer( - id: string, - userId: UserId, - permanent: boolean, - isUnassigned: boolean, - ) { - const asAdmin = this.organization?.canEditAllCiphers || isUnassigned; - return permanent - ? this.cipherService.deleteWithServer(id, userId, asAdmin) - : this.cipherService.softDeleteWithServer(id, userId, asAdmin); - } - - protected async repromptCipher(ciphers: CipherView[]) { - const notProtected = !ciphers.find((cipher) => cipher.reprompt !== CipherRepromptType.None); - - return notProtected || (await this.passwordRepromptService.showPasswordPrompt()); - } - - private refresh() { - this.refresh$.next(); - this.vaultItemsComponent?.clearSelection(); - } - - private go(queryParams: any = null) { - if (queryParams == null) { - queryParams = { - type: this.activeFilter.cipherType, - collectionId: this.activeFilter.collectionId, - deleted: this.activeFilter.isDeleted || null, - }; - } - - void this.router.navigate([], { - relativeTo: this.route, - queryParams: queryParams, - queryParamsHandling: "merge", - replaceUrl: true, - }); - } - - protected readonly CollectionDialogTabType = CollectionDialogTabType; - - private showMissingPermissionsError() { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("missingPermissions"), - }); - } -} - -/** - * Allows backwards compatibility with - * old links that used the original `cipherId` param - */ -const getCipherIdFromParams = (params: Params): string => { - return params["itemId"] || params["cipherId"]; -}; diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-routing.module.ts b/apps/web/src/app/admin-console/organizations/collections/vault-routing.module.ts index d529c4c31fe..7ad9f050d7b 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-routing.module.ts @@ -1,26 +1,19 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { canAccessVaultTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { organizationPermissionsGuard } from "../guards/org-permissions.guard"; -import { VaultComponent } from "./deprecated_vault.component"; -import { vNextVaultComponent } from "./vault.component"; +import { VaultComponent } from "./vault.component"; const routes: Routes = [ - ...featureFlaggedRoute({ - defaultComponent: VaultComponent, - flaggedComponent: vNextVaultComponent, - featureFlag: FeatureFlag.CollectionVaultRefactor, - routeOptions: { - data: { titleId: "vaults" }, - path: "", - canActivate: [organizationPermissionsGuard(canAccessVaultTab)], - }, - }), + { + data: { titleId: "vaults" }, + path: "", + canActivate: [organizationPermissionsGuard(canAccessVaultTab)], + component: VaultComponent, + }, ]; @NgModule({ diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 64aa6936468..51315b9a1a5 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -162,7 +162,7 @@ enum AddAccessStatusType { { provide: CipherFormConfigService, useClass: AdminConsoleCipherFormConfigService }, ], }) -export class vNextVaultComponent implements OnInit, OnDestroy { +export class VaultComponent implements OnInit, OnDestroy { protected Unassigned = Unassigned; trashCleanupWarning: string = this.i18nService.t( diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.module.ts b/apps/web/src/app/admin-console/organizations/collections/vault.module.ts index 92dbc5d832c..1a093ff8352 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.module.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.module.ts @@ -6,10 +6,9 @@ import { ViewComponent } from "../../../vault/individual-vault/view.component"; import { CollectionDialogComponent } from "../shared/components/collection-dialog"; import { CollectionNameBadgeComponent } from "./collection-badge"; -import { VaultComponent } from "./deprecated_vault.component"; import { GroupBadgeModule } from "./group-badge/group-badge.module"; import { VaultRoutingModule } from "./vault-routing.module"; -import { vNextVaultComponent } from "./vault.component"; +import { VaultComponent } from "./vault.component"; @NgModule({ imports: [ @@ -20,7 +19,6 @@ import { vNextVaultComponent } from "./vault.component"; OrganizationBadgeModule, CollectionDialogComponent, VaultComponent, - vNextVaultComponent, ViewComponent, ], }) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index bd874f934f0..67836befd7c 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -12,7 +12,6 @@ import { ServerConfig } from "../platform/abstractions/config/server-config"; export enum FeatureFlag { /* Admin Console Team */ CreateDefaultLocation = "pm-19467-create-default-location", - CollectionVaultRefactor = "pm-25030-resolve-ts-upgrade-errors", /* Auth */ PM22110_DisableAlternateLoginMethods = "pm-22110-disable-alternate-login-methods", @@ -74,7 +73,6 @@ const FALSE = false as boolean; export const DefaultFeatureFlagValue = { /* Admin Console Team */ [FeatureFlag.CreateDefaultLocation]: FALSE, - [FeatureFlag.CollectionVaultRefactor]: FALSE, /* Autofill */ [FeatureFlag.MacOsNativeCredentialSync]: FALSE, From dafbe7db1fd3602287d08f6cdae61f846f840d19 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Tue, 30 Sep 2025 11:59:19 -0400 Subject: [PATCH 10/83] add bold font to dt and add margin to dl (#16649) --- libs/components/src/tw-theme-preflight.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/components/src/tw-theme-preflight.css b/libs/components/src/tw-theme-preflight.css index e5f35885993..372c80e0881 100644 --- a/libs/components/src/tw-theme-preflight.css +++ b/libs/components/src/tw-theme-preflight.css @@ -54,6 +54,14 @@ display: none !important; } + dl { + @apply tw-mb-4; + } + + dt { + @apply tw-font-bold; + } + hr { border-color: rgba(0, 0, 0, 0.1); } From 6499ecb6ee9dcb81d29e9b3adf35c844f64cc0a7 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 30 Sep 2025 11:55:27 -0500 Subject: [PATCH 11/83] PM-26329 bug fixes to the cards (#16665) --- apps/web/src/locales/en/messages.json | 8 ++++---- .../dirt/access-intelligence/all-activity.component.html | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index e2bb463c939..2d70a79a7bf 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -59,8 +59,8 @@ "createNewLoginItem": { "message": "Create new login item" }, - "onceYouMarkCriticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -165,8 +165,8 @@ "membersAtRiskActivityDescription":{ "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index 6598d197172..8d564502ee4 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -22,7 +22,7 @@ Date: Tue, 30 Sep 2025 13:04:22 -0400 Subject: [PATCH 12/83] add back missing chevron (#16614) * add back missing chevron * add transform origin to center chevron correctly * update top position to center chevron properly --- libs/components/src/form-field/form-field.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/components/src/form-field/form-field.component.html b/libs/components/src/form-field/form-field.component.html index ae3bad40698..c2c92104727 100644 --- a/libs/components/src/form-field/form-field.component.html +++ b/libs/components/src/form-field/form-field.component.html @@ -56,7 +56,7 @@
- -
{{ "noItemsInArchive" | i18n }}
-

- {{ "archivedItemsDescription" | i18n }} + +

+ {{ (emptyState$ | async)?.title | i18n }} +
+

+ {{ (emptyState$ | async)?.description | i18n }}

-
{{ "noItemsInList" | i18n }}
- -
- - - - - - {{ "loading" | i18n }} - diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts deleted file mode 100644 index 431f8882505..00000000000 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ /dev/null @@ -1,360 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { - Component, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, - ViewChild, -} from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { firstValueFrom, from, Subject, switchMap, takeUntil } from "rxjs"; - -import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { - BillingInformation, - OrganizationBillingServiceAbstraction as OrganizationBillingService, - OrganizationInformation, - PaymentInformation, - PlanInformation, -} from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { - PaymentMethodType, - PlanType, - ProductTierType, - ProductType, -} from "@bitwarden/common/billing/enums"; -import { PreviewTaxAmountForOrganizationTrialRequest } from "@bitwarden/common/billing/models/request/tax"; -import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { ToastService } from "@bitwarden/components"; - -import { BillingSharedModule } from "../../shared"; -import { PaymentComponent } from "../../shared/payment/payment.component"; - -export type TrialOrganizationType = Exclude; - -export interface OrganizationInfo { - name: string; - email: string; - type: TrialOrganizationType | null; -} - -export interface OrganizationCreatedEvent { - organizationId: string; - planDescription: string; -} - -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -enum SubscriptionCadence { - Annual, - Monthly, -} - -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum SubscriptionProduct { - PasswordManager, - SecretsManager, -} - -@Component({ - selector: "app-trial-billing-step", - templateUrl: "trial-billing-step.component.html", - imports: [BillingSharedModule], -}) -export class TrialBillingStepComponent implements OnInit, OnDestroy { - @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(ManageTaxInformationComponent) taxInfoComponent: ManageTaxInformationComponent; - @Input() organizationInfo: OrganizationInfo; - @Input() subscriptionProduct: SubscriptionProduct = SubscriptionProduct.PasswordManager; - @Input() trialLength: number; - @Output() steppedBack = new EventEmitter(); - @Output() organizationCreated = new EventEmitter(); - - loading = true; - fetchingTaxAmount = false; - - annualCadence = SubscriptionCadence.Annual; - monthlyCadence = SubscriptionCadence.Monthly; - - formGroup = this.formBuilder.group({ - cadence: [SubscriptionCadence.Annual, Validators.required], - }); - formPromise: Promise; - - applicablePlans: PlanResponse[]; - annualPlan?: PlanResponse; - monthlyPlan?: PlanResponse; - - taxAmount = 0; - - private destroy$ = new Subject(); - - protected readonly SubscriptionProduct = SubscriptionProduct; - - constructor( - private apiService: ApiService, - private i18nService: I18nService, - private formBuilder: FormBuilder, - private messagingService: MessagingService, - private organizationBillingService: OrganizationBillingService, - private toastService: ToastService, - private taxService: TaxServiceAbstraction, - private accountService: AccountService, - ) {} - - async ngOnInit(): Promise { - const plans = await this.apiService.getPlans(); - this.applicablePlans = plans.data.filter(this.isApplicable); - this.annualPlan = this.findPlanFor(SubscriptionCadence.Annual); - this.monthlyPlan = this.findPlanFor(SubscriptionCadence.Monthly); - - if (this.trialLength === 0) { - this.formGroup.controls.cadence.valueChanges - .pipe( - switchMap((cadence) => from(this.previewTaxAmount(cadence))), - takeUntil(this.destroy$), - ) - .subscribe((taxAmount) => { - this.taxAmount = taxAmount; - }); - } - - this.loading = false; - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - async submit(): Promise { - if (!this.taxInfoComponent.validate()) { - return; - } - - this.formPromise = this.createOrganization(); - - const organizationId = await this.formPromise; - const planDescription = this.getPlanDescription(); - - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("organizationCreated"), - message: this.i18nService.t("organizationReadyToGo"), - }); - - this.organizationCreated.emit({ - organizationId, - planDescription, - }); - - // TODO: No one actually listening to this? - this.messagingService.send("organizationCreated", { organizationId }); - } - - async onTaxInformationChanged() { - if (this.trialLength === 0) { - this.taxAmount = await this.previewTaxAmount(this.formGroup.value.cadence); - } - - this.paymentComponent.showBankAccount = - this.taxInfoComponent.getTaxInformation().country === "US"; - if ( - !this.paymentComponent.showBankAccount && - this.paymentComponent.selected === PaymentMethodType.BankAccount - ) { - this.paymentComponent.select(PaymentMethodType.Card); - } - } - - protected getPriceFor(cadence: SubscriptionCadence): number { - const plan = this.findPlanFor(cadence); - return this.subscriptionProduct === SubscriptionProduct.PasswordManager - ? plan.PasswordManager.basePrice === 0 - ? plan.PasswordManager.seatPrice - : plan.PasswordManager.basePrice - : plan.SecretsManager.basePrice === 0 - ? plan.SecretsManager.seatPrice - : plan.SecretsManager.basePrice; - } - - protected stepBack() { - this.steppedBack.emit(); - } - - private async createOrganization(): Promise { - const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - const planResponse = this.findPlanFor(this.formGroup.value.cadence); - - const { type, token } = await this.paymentComponent.tokenize(); - const paymentMethod: [string, PaymentMethodType] = [token, type]; - - const organization: OrganizationInformation = { - name: this.organizationInfo.name, - billingEmail: this.organizationInfo.email, - initiationPath: - this.subscriptionProduct === SubscriptionProduct.PasswordManager - ? "Password Manager trial from marketing website" - : "Secrets Manager trial from marketing website", - }; - - const plan: PlanInformation = { - type: planResponse.type, - passwordManagerSeats: 1, - }; - - if (this.subscriptionProduct === SubscriptionProduct.SecretsManager) { - plan.subscribeToSecretsManager = true; - plan.isFromSecretsManagerTrial = true; - plan.secretsManagerSeats = 1; - } - - const payment: PaymentInformation = { - paymentMethod, - billing: this.getBillingInformationFromTaxInfoComponent(), - skipTrial: this.trialLength === 0, - }; - - const response = await this.organizationBillingService.purchaseSubscription( - { - organization, - plan, - payment, - }, - activeUserId, - ); - - return response.id; - } - - private productTypeToPlanTypeMap: { - [productType in TrialOrganizationType]: { - [cadence in SubscriptionCadence]?: PlanType; - }; - } = { - [ProductTierType.Enterprise]: { - [SubscriptionCadence.Annual]: PlanType.EnterpriseAnnually, - [SubscriptionCadence.Monthly]: PlanType.EnterpriseMonthly, - }, - [ProductTierType.Families]: { - [SubscriptionCadence.Annual]: PlanType.FamiliesAnnually, - // No monthly option for Families plan - }, - [ProductTierType.Teams]: { - [SubscriptionCadence.Annual]: PlanType.TeamsAnnually, - [SubscriptionCadence.Monthly]: PlanType.TeamsMonthly, - }, - [ProductTierType.TeamsStarter]: { - // No annual option for Teams Starter plan - [SubscriptionCadence.Monthly]: PlanType.TeamsStarter, - }, - }; - - private findPlanFor(cadence: SubscriptionCadence): PlanResponse | null { - const productType = this.organizationInfo.type; - const planType = this.productTypeToPlanTypeMap[productType]?.[cadence]; - return planType ? this.applicablePlans.find((plan) => plan.type === planType) : null; - } - - protected get showTaxIdField(): boolean { - switch (this.organizationInfo.type) { - case ProductTierType.Families: - return false; - default: - return true; - } - } - - private getBillingInformationFromTaxInfoComponent(): BillingInformation { - return { - postalCode: this.taxInfoComponent.getTaxInformation()?.postalCode, - country: this.taxInfoComponent.getTaxInformation()?.country, - taxId: this.taxInfoComponent.getTaxInformation()?.taxId, - addressLine1: this.taxInfoComponent.getTaxInformation()?.line1, - addressLine2: this.taxInfoComponent.getTaxInformation()?.line2, - city: this.taxInfoComponent.getTaxInformation()?.city, - state: this.taxInfoComponent.getTaxInformation()?.state, - }; - } - - private getPlanDescription(): string { - const plan = this.findPlanFor(this.formGroup.value.cadence); - const price = - this.subscriptionProduct === SubscriptionProduct.PasswordManager - ? plan.PasswordManager.basePrice === 0 - ? plan.PasswordManager.seatPrice - : plan.PasswordManager.basePrice - : plan.SecretsManager.basePrice === 0 - ? plan.SecretsManager.seatPrice - : plan.SecretsManager.basePrice; - - switch (this.formGroup.value.cadence) { - case SubscriptionCadence.Annual: - return `${this.i18nService.t("annual")} ($${price}/${this.i18nService.t("yr")})`; - case SubscriptionCadence.Monthly: - return `${this.i18nService.t("monthly")} ($${price}/${this.i18nService.t("monthAbbr")})`; - } - } - - private isApplicable(plan: PlanResponse): boolean { - const hasCorrectProductType = - plan.productTier === ProductTierType.Enterprise || - plan.productTier === ProductTierType.Families || - plan.productTier === ProductTierType.Teams || - plan.productTier === ProductTierType.TeamsStarter; - const notDisabledOrLegacy = !plan.disabled && !plan.legacyYear; - return hasCorrectProductType && notDisabledOrLegacy; - } - - private previewTaxAmount = async (cadence: SubscriptionCadence): Promise => { - this.fetchingTaxAmount = true; - - if (!this.taxInfoComponent.validate()) { - this.fetchingTaxAmount = false; - return 0; - } - - const plan = this.findPlanFor(cadence); - - const productType = - this.subscriptionProduct === SubscriptionProduct.PasswordManager - ? ProductType.PasswordManager - : ProductType.SecretsManager; - - const taxInformation = this.taxInfoComponent.getTaxInformation(); - - const request: PreviewTaxAmountForOrganizationTrialRequest = { - planType: plan.type, - productType, - taxInformation: { - ...taxInformation, - }, - }; - - const response = await this.taxService.previewTaxAmountForOrganizationTrial(request); - this.fetchingTaxAmount = false; - return response; - }; - - get price() { - return this.getPriceFor(this.formGroup.value.cadence); - } - - get total() { - return this.price + this.taxAmount; - } - - get interval() { - return this.formGroup.value.cadence === SubscriptionCadence.Annual ? "year" : "month"; - } -} diff --git a/apps/web/src/app/billing/clients/index.ts b/apps/web/src/app/billing/clients/index.ts index ff962abcbf3..17f64248cfa 100644 --- a/apps/web/src/app/billing/clients/index.ts +++ b/apps/web/src/app/billing/clients/index.ts @@ -1,2 +1,3 @@ export * from "./organization-billing.client"; export * from "./subscriber-billing.client"; +export * from "./tax.client"; diff --git a/apps/web/src/app/billing/clients/subscriber-billing.client.ts b/apps/web/src/app/billing/clients/subscriber-billing.client.ts index 18ca215ef0c..107a8ccc728 100644 --- a/apps/web/src/app/billing/clients/subscriber-billing.client.ts +++ b/apps/web/src/app/billing/clients/subscriber-billing.client.ts @@ -82,6 +82,24 @@ export class SubscriberBillingClient { return data ? new MaskedPaymentMethodResponse(data).value : null; }; + restartSubscription = async ( + subscriber: BitwardenSubscriber, + paymentMethod: TokenizedPaymentMethod, + billingAddress: BillingAddress, + ): Promise => { + const path = `${this.getEndpoint(subscriber)}/subscription/restart`; + await this.apiService.send( + "POST", + path, + { + paymentMethod, + billingAddress, + }, + true, + false, + ); + }; + updateBillingAddress = async ( subscriber: BitwardenSubscriber, billingAddress: BillingAddress, diff --git a/apps/web/src/app/billing/clients/tax.client.ts b/apps/web/src/app/billing/clients/tax.client.ts new file mode 100644 index 00000000000..09debd5a210 --- /dev/null +++ b/apps/web/src/app/billing/clients/tax.client.ts @@ -0,0 +1,131 @@ +import { Injectable } from "@angular/core"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { BillingAddress } from "@bitwarden/web-vault/app/billing/payment/types"; + +class TaxAmountResponse extends BaseResponse implements TaxAmounts { + tax: number; + total: number; + + constructor(response: any) { + super(response); + + this.tax = this.getResponseProperty("Tax"); + this.total = this.getResponseProperty("Total"); + } +} + +export type OrganizationSubscriptionPlan = { + tier: "families" | "teams" | "enterprise"; + cadence: "annually" | "monthly"; +}; + +export type OrganizationSubscriptionPurchase = OrganizationSubscriptionPlan & { + passwordManager: { + seats: number; + additionalStorage: number; + sponsored: boolean; + }; + secretsManager?: { + seats: number; + additionalServiceAccounts: number; + standalone: boolean; + }; +}; + +export type OrganizationSubscriptionUpdate = { + passwordManager?: { + seats?: number; + additionalStorage?: number; + }; + secretsManager?: { + seats?: number; + additionalServiceAccounts?: number; + }; +}; + +export interface TaxAmounts { + tax: number; + total: number; +} + +@Injectable() +export class TaxClient { + constructor(private apiService: ApiService) {} + + previewTaxForOrganizationSubscriptionPurchase = async ( + purchase: OrganizationSubscriptionPurchase, + billingAddress: BillingAddress, + ): Promise => { + const json = await this.apiService.send( + "POST", + "/billing/tax/organizations/subscriptions/purchase", + { + purchase, + billingAddress, + }, + true, + true, + ); + + return new TaxAmountResponse(json); + }; + + previewTaxForOrganizationSubscriptionPlanChange = async ( + organizationId: string, + plan: { + tier: "families" | "teams" | "enterprise"; + cadence: "annually" | "monthly"; + }, + billingAddress: BillingAddress | null, + ): Promise => { + const json = await this.apiService.send( + "POST", + `/billing/tax/organizations/${organizationId}/subscription/plan-change`, + { + plan, + billingAddress, + }, + true, + true, + ); + + return new TaxAmountResponse(json); + }; + + previewTaxForOrganizationSubscriptionUpdate = async ( + organizationId: string, + update: OrganizationSubscriptionUpdate, + ): Promise => { + const json = await this.apiService.send( + "POST", + `/billing/tax/organizations/${organizationId}/subscription/update`, + { + update, + }, + true, + true, + ); + + return new TaxAmountResponse(json); + }; + + previewTaxForPremiumSubscriptionPurchase = async ( + additionalStorage: number, + billingAddress: BillingAddress, + ): Promise => { + const json = await this.apiService.send( + "POST", + `/billing/tax/premium/subscriptions/purchase`, + { + additionalStorage, + billingAddress, + }, + true, + true, + ); + + return new TaxAmountResponse(json); + }; +} diff --git a/apps/web/src/app/billing/index.ts b/apps/web/src/app/billing/index.ts index 217f1e05be9..a3047bbab6a 100644 --- a/apps/web/src/app/billing/index.ts +++ b/apps/web/src/app/billing/index.ts @@ -1,2 +1 @@ export { OrganizationPlansComponent } from "./organizations"; -export { TaxInfoComponent } from "./shared"; diff --git a/apps/web/src/app/billing/individual/index.ts b/apps/web/src/app/billing/individual/index.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts index 87b342ed997..bb0ca60b677 100644 --- a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts +++ b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts @@ -3,8 +3,6 @@ import { RouterModule, Routes } from "@angular/router"; import { AccountPaymentDetailsComponent } from "@bitwarden/web-vault/app/billing/individual/payment-details/account-payment-details.component"; -import { PaymentMethodComponent } from "../shared"; - import { BillingHistoryViewComponent } from "./billing-history-view.component"; import { PremiumComponent } from "./premium/premium.component"; import { SubscriptionComponent } from "./subscription.component"; @@ -27,11 +25,6 @@ const routes: Routes = [ component: PremiumComponent, data: { titleId: "goPremium" }, }, - { - path: "payment-method", - component: PaymentMethodComponent, - data: { titleId: "paymentMethod" }, - }, { path: "payment-details", component: AccountPaymentDetailsComponent, diff --git a/apps/web/src/app/billing/individual/individual-billing.module.ts b/apps/web/src/app/billing/individual/individual-billing.module.ts index ad75da00c99..20f2a6cc143 100644 --- a/apps/web/src/app/billing/individual/individual-billing.module.ts +++ b/apps/web/src/app/billing/individual/individual-billing.module.ts @@ -1,5 +1,10 @@ import { NgModule } from "@angular/core"; +import { + EnterBillingAddressComponent, + EnterPaymentMethodComponent, +} from "@bitwarden/web-vault/app/billing/payment/components"; + import { HeaderModule } from "../../layouts/header/header.module"; import { BillingSharedModule } from "../shared"; @@ -10,7 +15,13 @@ import { SubscriptionComponent } from "./subscription.component"; import { UserSubscriptionComponent } from "./user-subscription.component"; @NgModule({ - imports: [IndividualBillingRoutingModule, BillingSharedModule, HeaderModule], + imports: [ + IndividualBillingRoutingModule, + BillingSharedModule, + HeaderModule, + EnterPaymentMethodComponent, + EnterBillingAddressComponent, + ], declarations: [ SubscriptionComponent, BillingHistoryViewComponent, diff --git a/apps/web/src/app/billing/individual/payment-details/account-payment-details.component.ts b/apps/web/src/app/billing/individual/payment-details/account-payment-details.component.ts index 9f46d9d3909..ca7902542de 100644 --- a/apps/web/src/app/billing/individual/payment-details/account-payment-details.component.ts +++ b/apps/web/src/app/billing/individual/payment-details/account-payment-details.component.ts @@ -1,22 +1,7 @@ import { Component } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { - BehaviorSubject, - EMPTY, - filter, - from, - map, - merge, - Observable, - shareReplay, - switchMap, - tap, -} from "rxjs"; -import { catchError } from "rxjs/operators"; +import { BehaviorSubject, filter, merge, Observable, shareReplay, switchMap, tap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { HeaderModule } from "../../../layouts/header/header.module"; import { SharedModule } from "../../../shared"; @@ -28,13 +13,6 @@ import { import { MaskedPaymentMethod } from "../../payment/types"; import { mapAccountToSubscriber, BitwardenSubscriber } from "../../types"; -class RedirectError { - constructor( - public path: string[], - public relativeTo: ActivatedRoute, - ) {} -} - type View = { account: BitwardenSubscriber; paymentMethod: MaskedPaymentMethod | null; @@ -56,23 +34,11 @@ export class AccountPaymentDetailsComponent { private viewState$ = new BehaviorSubject(null); private load$: Observable = this.accountService.activeAccount$.pipe( - switchMap((account) => - this.configService - .getFeatureFlag$(FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout) - .pipe( - map((managePaymentDetailsOutsideCheckout) => { - if (!managePaymentDetailsOutsideCheckout) { - throw new RedirectError(["../payment-method"], this.activatedRoute); - } - return account; - }), - ), - ), mapAccountToSubscriber, switchMap(async (account) => { const [paymentMethod, credit] = await Promise.all([ - this.billingClient.getPaymentMethod(account), - this.billingClient.getCredit(account), + this.subscriberBillingClient.getPaymentMethod(account), + this.subscriberBillingClient.getCredit(account), ]); return { @@ -82,14 +48,6 @@ export class AccountPaymentDetailsComponent { }; }), shareReplay({ bufferSize: 1, refCount: false }), - catchError((error: unknown) => { - if (error instanceof RedirectError) { - return from(this.router.navigate(error.path, { relativeTo: error.relativeTo })).pipe( - switchMap(() => EMPTY), - ); - } - throw error; - }), ); view$: Observable = merge( @@ -99,10 +57,7 @@ export class AccountPaymentDetailsComponent { constructor( private accountService: AccountService, - private activatedRoute: ActivatedRoute, - private billingClient: SubscriberBillingClient, - private configService: ConfigService, - private router: Router, + private subscriberBillingClient: SubscriberBillingClient, ) {} setPaymentMethod = (paymentMethod: MaskedPaymentMethod) => { diff --git a/apps/web/src/app/billing/individual/premium/premium.component.html b/apps/web/src/app/billing/individual/premium/premium.component.html index 3f0f97541df..52ebe7803df 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.html +++ b/apps/web/src/app/billing/individual/premium/premium.component.html @@ -70,7 +70,7 @@ (onLicenseFileUploaded)="onLicenseFileSelectedChanged()" /> -
+

{{ "addons" | i18n }}

@@ -93,15 +93,25 @@

{{ "summary" | i18n }}

{{ "premiumMembership" | i18n }}: {{ premiumPrice | currency: "$" }}
- {{ "additionalStorageGb" | i18n }}: {{ addOnFormGroup.value.additionalStorage || 0 }} GB × + {{ "additionalStorageGb" | i18n }}: {{ formGroup.value.additionalStorage || 0 }} GB × {{ storageGBPrice | currency: "$" }} = {{ additionalStorageCost | currency: "$" }}

{{ "paymentInformation" | i18n }}

- - +
+ + + + +
{{ "planPrice" | i18n }}: {{ subtotal | currency: "USD $" }} diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/premium.component.ts index 974c22455ff..d5062e34881 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium.component.ts @@ -9,36 +9,34 @@ import { debounceTime } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { PreviewIndividualInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-individual-invoice.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { ToastService } from "@bitwarden/components"; - -import { PaymentComponent } from "../../shared/payment/payment.component"; -import { TaxInfoComponent } from "../../shared/tax-info.component"; +import { TaxClient } from "@bitwarden/web-vault/app/billing/clients"; +import { + EnterBillingAddressComponent, + EnterPaymentMethodComponent, + getBillingAddressFromForm, +} from "@bitwarden/web-vault/app/billing/payment/components"; +import { tokenizablePaymentMethodToLegacyEnum } from "@bitwarden/web-vault/app/billing/payment/types"; @Component({ templateUrl: "./premium.component.html", standalone: false, + providers: [TaxClient], }) export class PremiumComponent { - @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(TaxInfoComponent) taxInfoComponent: TaxInfoComponent; + @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent!: EnterPaymentMethodComponent; protected hasPremiumFromAnyOrganization$: Observable; - protected addOnFormGroup = new FormGroup({ + protected formGroup = new FormGroup({ additionalStorage: new FormControl(0, [Validators.min(0), Validators.max(99)]), - }); - - protected licenseFormGroup = new FormGroup({ - file: new FormControl(null, [Validators.required]), + paymentMethod: EnterPaymentMethodComponent.getFormGroup(), + billingAddress: EnterBillingAddressComponent.getFormGroup(), }); protected cloudWebVaultURL: string; @@ -53,16 +51,14 @@ export class PremiumComponent { private activatedRoute: ActivatedRoute, private apiService: ApiService, private billingAccountProfileStateService: BillingAccountProfileStateService, - private configService: ConfigService, private environmentService: EnvironmentService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private router: Router, private syncService: SyncService, private toastService: ToastService, - private tokenService: TokenService, - private taxService: TaxServiceAbstraction, private accountService: AccountService, + private taxClient: TaxClient, ) { this.isSelfHost = this.platformUtilsService.isSelfHost(); @@ -93,11 +89,13 @@ export class PremiumComponent { ) .subscribe(); - this.addOnFormGroup.controls.additionalStorage.valueChanges - .pipe(debounceTime(1000), takeUntilDestroyed()) - .subscribe(() => { - this.refreshSalesTax(); - }); + this.formGroup.valueChanges + .pipe( + debounceTime(1000), + switchMap(async () => await this.refreshSalesTax()), + takeUntilDestroyed(), + ) + .subscribe(); } finalizeUpgrade = async () => { @@ -117,53 +115,21 @@ export class PremiumComponent { navigateToSubscriptionPage = (): Promise => this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute }); - onLicenseFileSelected = (event: Event): void => { - const element = event.target as HTMLInputElement; - this.licenseFormGroup.value.file = element.files.length > 0 ? element.files[0] : null; - }; - - submitPremiumLicense = async (): Promise => { - this.licenseFormGroup.markAllAsTouched(); - - if (this.licenseFormGroup.invalid) { - return this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("selectFile"), - }); - } - - const emailVerified = await this.tokenService.getEmailVerified(); - if (!emailVerified) { - return this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("verifyEmailFirst"), - }); - } - - const formData = new FormData(); - formData.append("license", this.licenseFormGroup.value.file); - - await this.apiService.postAccountLicense(formData); - await this.finalizeUpgrade(); - await this.postFinalizeUpgrade(); - }; - submitPayment = async (): Promise => { - this.taxInfoComponent.taxFormGroup.markAllAsTouched(); - if (this.taxInfoComponent.taxFormGroup.invalid) { + if (this.formGroup.invalid) { return; } - const { type, token } = await this.paymentComponent.tokenize(); + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + + const legacyEnum = tokenizablePaymentMethodToLegacyEnum(paymentMethod.type); const formData = new FormData(); - formData.append("paymentMethodType", type.toString()); - formData.append("paymentToken", token); - formData.append("additionalStorageGb", this.addOnFormGroup.value.additionalStorage.toString()); - formData.append("country", this.taxInfoComponent.country); - formData.append("postalCode", this.taxInfoComponent.postalCode); + formData.append("paymentMethodType", legacyEnum.toString()); + formData.append("paymentToken", paymentMethod.token); + formData.append("additionalStorageGb", this.formGroup.value.additionalStorage.toString()); + formData.append("country", this.formGroup.value.billingAddress.country); + formData.append("postalCode", this.formGroup.value.billingAddress.postalCode); await this.apiService.postPremium(formData); await this.finalizeUpgrade(); @@ -171,7 +137,7 @@ export class PremiumComponent { }; protected get additionalStorageCost(): number { - return this.storageGBPrice * this.addOnFormGroup.value.additionalStorage; + return this.storageGBPrice * this.formGroup.value.additionalStorage; } protected get premiumURL(): string { @@ -190,35 +156,18 @@ export class PremiumComponent { await this.postFinalizeUpgrade(); } - private refreshSalesTax(): void { - if (!this.taxInfoComponent.country || !this.taxInfoComponent.postalCode) { + private async refreshSalesTax(): Promise { + if (this.formGroup.invalid) { return; } - const request: PreviewIndividualInvoiceRequest = { - passwordManager: { - additionalStorage: this.addOnFormGroup.value.additionalStorage, - }, - taxInformation: { - postalCode: this.taxInfoComponent.postalCode, - country: this.taxInfoComponent.country, - }, - }; - this.taxService - .previewIndividualInvoice(request) - .then((invoice) => { - this.estimatedTax = invoice.taxAmount; - }) - .catch((error) => { - this.toastService.showToast({ - title: "", - variant: "error", - message: this.i18nService.t(error.message), - }); - }); - } + const billingAddress = getBillingAddressFromForm(this.formGroup.controls.billingAddress); - protected onTaxInformationChanged(): void { - this.refreshSalesTax(); + const taxAmounts = await this.taxClient.previewTaxForPremiumSubscriptionPurchase( + this.formGroup.value.additionalStorage, + billingAddress, + ); + + this.estimatedTax = taxAmounts.tax; } } diff --git a/apps/web/src/app/billing/individual/subscription.component.html b/apps/web/src/app/billing/individual/subscription.component.html index fa2eb0412a9..f9a46cf56ad 100644 --- a/apps/web/src/app/billing/individual/subscription.component.html +++ b/apps/web/src/app/billing/individual/subscription.component.html @@ -3,10 +3,7 @@ {{ "subscription" | i18n }} - @let paymentMethodPageData = paymentDetailsPageData$ | async; - {{ - paymentMethodPageData.textKey | i18n - }} + {{ "paymentDetails" | i18n }} {{ "billingHistory" | i18n }} diff --git a/apps/web/src/app/billing/individual/subscription.component.ts b/apps/web/src/app/billing/individual/subscription.component.ts index c6a20a9f6a3..2a08ec85127 100644 --- a/apps/web/src/app/billing/individual/subscription.component.ts +++ b/apps/web/src/app/billing/individual/subscription.component.ts @@ -1,12 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { map, Observable, switchMap } from "rxjs"; +import { Observable, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @Component({ @@ -15,32 +13,16 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl }) export class SubscriptionComponent implements OnInit { hasPremium$: Observable; - paymentDetailsPageData$: Observable<{ - route: string; - textKey: string; - }>; - selfHosted: boolean; constructor( private platformUtilsService: PlatformUtilsService, billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, - private configService: ConfigService, ) { this.hasPremium$ = accountService.activeAccount$.pipe( switchMap((account) => billingAccountProfileStateService.hasPremiumPersonally$(account.id)), ); - - this.paymentDetailsPageData$ = this.configService - .getFeatureFlag$(FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout) - .pipe( - map((managePaymentDetailsOutsideCheckout) => - managePaymentDetailsOutsideCheckout - ? { route: "payment-details", textKey: "paymentDetails" } - : { route: "payment-method", textKey: "paymentMethod" }, - ), - ); } ngOnInit() { diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index f899b8eccb4..abd7bdb155a 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -328,24 +328,60 @@ *ngIf="formGroup.value.productTier !== productTypes.Free || isSubscriptionCanceled" >

{{ "paymentMethod" | i18n }}

-

- - {{ paymentSource?.description }} - - {{ "changePaymentMethod" | i18n }} - +

+ @switch (paymentMethod.type) { + @case ("bankAccount") { + + {{ paymentMethod.bankName }}, *{{ paymentMethod.last4 }} + @if (paymentMethod.hostedVerificationUrl) { + - {{ "unverified" | i18n }} + } + + {{ "changePaymentMethod" | i18n }} + + } + @case ("card") { +

+ @let cardBrandIcon = getCardBrandIcon(); + @if (cardBrandIcon !== null) { + + } @else { + + } + {{ paymentMethod.brand | titlecase }}, *{{ paymentMethod.last4 }}, + {{ paymentMethod.expiration }} + + {{ "changePaymentMethod" | i18n }} + +

+ } + @case ("payPal") { + + {{ paymentMethod.email }} + + {{ "changePaymentMethod" | i18n }} + + } + }

- - + + + +

diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 6fc2dc57ba2..2b5c27e0f09 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -12,9 +12,9 @@ import { } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { firstValueFrom, map, Subject, switchMap, takeUntil } from "rxjs"; +import { combineLatest, firstValueFrom, map, Subject, switchMap, takeUntil } from "rxjs"; +import { debounceTime } from "rxjs/operators"; -import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { @@ -28,28 +28,8 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { - BillingApiServiceAbstraction, - BillingInformation, - OrganizationBillingServiceAbstraction as OrganizationBillingService, - OrganizationInformation, - PaymentInformation, - PlanInformation, -} from "@bitwarden/common/billing/abstractions"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { - PaymentMethodType, - PlanInterval, - PlanType, - ProductTierType, -} from "@bitwarden/common/billing/enums"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; +import { PlanInterval, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; -import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -57,6 +37,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { OrganizationId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { + CardComponent, DIALOG_DATA, DialogConfig, DialogRef, @@ -64,11 +45,25 @@ import { ToastService, } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; -import { UserId } from "@bitwarden/user-core"; +import { + OrganizationSubscriptionPlan, + SubscriberBillingClient, + TaxClient, +} from "@bitwarden/web-vault/app/billing/clients"; +import { + EnterBillingAddressComponent, + EnterPaymentMethodComponent, + getBillingAddressFromForm, +} from "@bitwarden/web-vault/app/billing/payment/components"; +import { + BillingAddress, + getCardBrandIcon, + MaskedPaymentMethod, +} from "@bitwarden/web-vault/app/billing/payment/types"; +import { BitwardenSubscriber } from "@bitwarden/web-vault/app/billing/types"; import { BillingNotificationService } from "../services/billing-notification.service"; import { BillingSharedModule } from "../shared/billing-shared.module"; -import { PaymentComponent } from "../shared/payment/payment.component"; type ChangePlanDialogParams = { organizationId: string; @@ -111,11 +106,16 @@ interface OnSuccessArgs { @Component({ templateUrl: "./change-plan-dialog.component.html", - imports: [BillingSharedModule], + imports: [ + BillingSharedModule, + EnterPaymentMethodComponent, + EnterBillingAddressComponent, + CardComponent, + ], + providers: [SubscriberBillingClient, TaxClient], }) export class ChangePlanDialogComponent implements OnInit, OnDestroy { - @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(ManageTaxInformationComponent) taxComponent: ManageTaxInformationComponent; + @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent: EnterPaymentMethodComponent; @Input() acceptingSponsorship = false; @Input() organizationId: string; @@ -172,7 +172,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { clientOwnerEmail: ["", [Validators.email]], plan: [this.plan], productTier: [this.productTier], - // planInterval: [1], + }); + + billingFormGroup = this.formBuilder.group({ + paymentMethod: EnterPaymentMethodComponent.getFormGroup(), + billingAddress: EnterBillingAddressComponent.getFormGroup(), }); planType: string; @@ -183,7 +187,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { secretsManagerPlans: PlanResponse[]; organization: Organization; sub: OrganizationSubscriptionResponse; - billing: BillingResponse; dialogHeaderName: string; currentPlanName: string; showPayment: boolean = false; @@ -191,15 +194,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { currentPlan: PlanResponse; isCardStateDisabled = false; focusedIndex: number | null = null; - accountCredit: number; - paymentSource?: PaymentSourceResponse; plans: ListResponse; isSubscriptionCanceled: boolean = false; secretsManagerTotal: number; - private destroy$ = new Subject(); + paymentMethod: MaskedPaymentMethod | null; + billingAddress: BillingAddress | null; - protected taxInformation: TaxInformation; + private destroy$ = new Subject(); constructor( @Inject(DIALOG_DATA) private dialogParams: ChangePlanDialogParams, @@ -215,11 +217,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { private messagingService: MessagingService, private formBuilder: FormBuilder, private organizationApiService: OrganizationApiServiceAbstraction, - private billingApiService: BillingApiServiceAbstraction, - private taxService: TaxServiceAbstraction, private accountService: AccountService, - private organizationBillingService: OrganizationBillingService, private billingNotificationService: BillingNotificationService, + private subscriberBillingClient: SubscriberBillingClient, + private taxClient: TaxClient, ) {} async ngOnInit(): Promise { @@ -242,10 +243,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ); if (this.sub?.subscription?.status !== "canceled") { try { - const { accountCredit, paymentSource } = - await this.billingApiService.getOrganizationPaymentMethod(this.organizationId); - this.accountCredit = accountCredit; - this.paymentSource = paymentSource; + const subscriber: BitwardenSubscriber = { type: "organization", data: this.organization }; + const [paymentMethod, billingAddress] = await Promise.all([ + this.subscriberBillingClient.getPaymentMethod(subscriber), + this.subscriberBillingClient.getBillingAddress(subscriber), + ]); + + this.paymentMethod = paymentMethod; + this.billingAddress = billingAddress; } catch (error) { this.billingNotificationService.handleError(error); } @@ -307,15 +312,24 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ? 0 : (this.sub?.customerDiscount?.percentOff ?? 0); - this.setInitialPlanSelection(); - this.loading = false; - - const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); - this.taxInformation = TaxInformation.from(taxInfo); - + await this.setInitialPlanSelection(); if (!this.isSubscriptionCanceled) { - this.refreshSalesTax(); + await this.refreshSalesTax(); } + + combineLatest([ + this.billingFormGroup.controls.billingAddress.controls.country.valueChanges, + this.billingFormGroup.controls.billingAddress.controls.postalCode.valueChanges, + this.billingFormGroup.controls.billingAddress.controls.taxId.valueChanges, + ]) + .pipe( + debounceTime(1000), + switchMap(async () => await this.refreshSalesTax()), + takeUntil(this.destroy$), + ) + .subscribe(); + + this.loading = false; } resolveHeaderName(subscription: OrganizationSubscriptionResponse): string { @@ -333,10 +347,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ); } - setInitialPlanSelection() { + async setInitialPlanSelection() { this.focusedIndex = this.selectableProducts.length - 1; if (!this.isSubscriptionCanceled) { - this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + await this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); } } @@ -344,10 +358,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return this.selectableProducts.find((product) => product.productTier === productTier); } - isPaymentSourceEmpty() { - return this.paymentSource === null || this.paymentSource === undefined; - } - isSecretsManagerTrial(): boolean { return ( this.sub?.subscription?.items?.some((item) => @@ -356,13 +366,13 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { ); } - planTypeChanged() { - this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); + async planTypeChanged() { + await this.selectPlan(this.getPlanByType(ProductTierType.Enterprise)); } - updateInterval(event: number) { + async updateInterval(event: number) { this.selectedInterval = event; - this.planTypeChanged(); + await this.planTypeChanged(); } protected getPlanIntervals() { @@ -460,7 +470,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } } - protected selectPlan(plan: PlanResponse) { + protected async selectPlan(plan: PlanResponse) { if ( this.selectedInterval === PlanInterval.Monthly && plan.productTier == ProductTierType.Families @@ -475,7 +485,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.formGroup.patchValue({ productTier: plan.productTier }); try { - this.refreshSalesTax(); + await this.refreshSalesTax(); } catch { this.estimatedTax = 0; } @@ -489,19 +499,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { get upgradeRequiresPaymentMethod() { const isFreeTier = this.organization?.productTierType === ProductTierType.Free; const shouldHideFree = !this.showFree; - const hasNoPaymentSource = !this.paymentSource; + const hasNoPaymentSource = !this.paymentMethod; return isFreeTier && shouldHideFree && hasNoPaymentSource; } - get selectedSecretsManagerPlan() { - let planResponse: PlanResponse; - if (this.secretsManagerPlans) { - return this.secretsManagerPlans.find((plan) => plan.type === this.selectedPlan.type); - } - return planResponse; - } - get selectedPlanInterval() { if (this.isSubscriptionCanceled) { return this.currentPlan.isAnnual ? "year" : "month"; @@ -591,8 +593,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return 0; } - const result = plan.PasswordManager.seatPrice * Math.abs(this.sub?.seats || 0); - return result; + return plan.PasswordManager.seatPrice * Math.abs(this.sub?.seats || 0); } secretsManagerSeatTotal(plan: PlanResponse, seats: number): number { @@ -746,39 +747,22 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.formGroup.controls.additionalSeats.setValue(1); } - changedCountry() { - this.paymentComponent.showBankAccount = this.taxInformation.country === "US"; - - if ( - !this.paymentComponent.showBankAccount && - this.paymentComponent.selected === PaymentMethodType.BankAccount - ) { - this.paymentComponent.select(PaymentMethodType.Card); - } - } - - protected taxInformationChanged(event: TaxInformation): void { - this.taxInformation = event; - this.changedCountry(); - this.refreshSalesTax(); - } - submit = async () => { - if (this.taxComponent !== undefined && !this.taxComponent.validate()) { - this.taxComponent.markAllAsTouched(); + this.formGroup.markAllAsTouched(); + this.billingFormGroup.markAllAsTouched(); + if (this.formGroup.invalid || (this.billingFormGroup.invalid && !this.paymentMethod)) { return; } const doSubmit = async (): Promise => { - const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - let orgId: string = null; + let orgId: string; const sub = this.sub?.subscription; const isCanceled = sub?.status === "canceled"; const isCancelledDowngradedToFreeOrg = sub?.cancelled && this.organization.productTierType === ProductTierType.Free; if (isCanceled || isCancelledDowngradedToFreeOrg) { - await this.restartSubscription(activeUserId); + await this.restartSubscription(); orgId = this.organizationId; } else { orgId = await this.updateOrganization(); @@ -795,9 +779,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { await this.syncService.fullSync(true); if (!this.acceptingSponsorship && !this.isInTrialFlow) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/organizations/" + orgId + "/billing/subscription"]); + await this.router.navigate(["/organizations/" + orgId + "/billing/subscription"]); } if (this.isInTrialFlow) { @@ -818,46 +800,13 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.dialogRef.close(); }; - private async restartSubscription(activeUserId: UserId) { - const org = await this.organizationApiService.get(this.organizationId); - const organization: OrganizationInformation = { - name: org.name, - billingEmail: org.billingEmail, - }; - - const filteredPlan = this.plans.data - .filter((plan) => plan.productTier === this.selectedPlan.productTier && !plan.legacyYear) - .find((plan) => { - const isSameBillingCycle = plan.isAnnual === this.selectedPlan.isAnnual; - return isSameBillingCycle; - }); - - const plan: PlanInformation = { - type: filteredPlan.type, - passwordManagerSeats: org.seats, - }; - - if (org.useSecretsManager) { - plan.subscribeToSecretsManager = true; - plan.secretsManagerSeats = org.smSeats; - } - - const { type, token } = await this.paymentComponent.tokenize(); - const paymentMethod: [string, PaymentMethodType] = [token, type]; - - const payment: PaymentInformation = { + private async restartSubscription() { + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + const billingAddress = getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress); + await this.subscriberBillingClient.restartSubscription( + { type: "organization", data: this.organization }, paymentMethod, - billing: this.getBillingInformationFromTaxInfoComponent(), - }; - - await this.organizationBillingService.restartSubscription( - this.organization.id, - { - organization, - plan, - payment, - }, - activeUserId, + billingAddress, ); } @@ -875,25 +824,25 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; if (this.showPayment) { - request.billingAddressCountry = this.taxInformation.country; - request.billingAddressPostalCode = this.taxInformation.postalCode; + request.billingAddressCountry = this.billingFormGroup.controls.billingAddress.value.country; + request.billingAddressPostalCode = + this.billingFormGroup.controls.billingAddress.value.postalCode; } // Secrets Manager this.buildSecretsManagerRequest(request); - if (this.upgradeRequiresPaymentMethod || this.showPayment || this.isPaymentSourceEmpty()) { - const tokenizedPaymentSource = await this.paymentComponent.tokenize(); - const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); - updatePaymentMethodRequest.paymentSource = tokenizedPaymentSource; - updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( - this.taxInformation, + if (this.upgradeRequiresPaymentMethod || this.showPayment || !this.paymentMethod) { + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + const billingAddress = getBillingAddressFromForm( + this.billingFormGroup.controls.billingAddress, ); - await this.billingApiService.updateOrganizationPaymentMethod( - this.organizationId, - updatePaymentMethodRequest, - ); + const subscriber: BitwardenSubscriber = { type: "organization", data: this.organization }; + await Promise.all([ + this.subscriberBillingClient.updatePaymentMethod(subscriber, paymentMethod, null), + this.subscriberBillingClient.updateBillingAddress(subscriber, billingAddress), + ]); } // Backfill pub/priv key if necessary @@ -931,18 +880,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return text; } - private getBillingInformationFromTaxInfoComponent(): BillingInformation { - return { - country: this.taxInformation.country, - postalCode: this.taxInformation.postalCode, - taxId: this.taxInformation.taxId, - addressLine1: this.taxInformation.line1, - addressLine2: this.taxInformation.line2, - city: this.taxInformation.city, - state: this.taxInformation.state, - }; - } - private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void { request.useSecretsManager = this.organization.useSecretsManager; if (!this.organization.useSecretsManager) { @@ -1002,25 +939,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } calculateTotalAppliedDiscount(total: number) { - const discountedTotal = total * (this.discountPercentageFromSub / 100); - return discountedTotal; - } - - get paymentSourceClasses() { - if (this.paymentSource == null) { - return []; - } - switch (this.paymentSource.type) { - case PaymentMethodType.Card: - return ["bwi-credit-card"]; - case PaymentMethodType.BankAccount: - case PaymentMethodType.Check: - return ["bwi-billing"]; - case PaymentMethodType.PayPal: - return ["bwi-paypal text-primary"]; - default: - return []; - } + return total * (this.discountPercentageFromSub / 100); } resolvePlanName(productTier: ProductTierType) { @@ -1064,9 +983,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } } - onFocus(index: number) { + async onFocus(index: number) { this.focusedIndex = index; - this.selectPlan(this.selectableProducts[index]); + await this.selectPlan(this.selectableProducts[index]); } isCardDisabled(index: number): boolean { @@ -1078,58 +997,44 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return index; } - private refreshSalesTax(): void { - if ( - this.taxInformation === undefined || - !this.taxInformation.country || - !this.taxInformation.postalCode - ) { + private async refreshSalesTax(): Promise { + if (this.billingFormGroup.controls.billingAddress.invalid && !this.billingAddress) { return; } - const request: PreviewOrganizationInvoiceRequest = { - organizationId: this.organizationId, - passwordManager: { - additionalStorage: 0, - plan: this.selectedPlan?.type, - seats: this.sub.seats, - }, - taxInformation: { - postalCode: this.taxInformation.postalCode, - country: this.taxInformation.country, - taxId: this.taxInformation.taxId, - }, + const getPlanFromLegacyEnum = (planType: PlanType): OrganizationSubscriptionPlan => { + switch (planType) { + case PlanType.FamiliesAnnually: + return { tier: "families", cadence: "annually" }; + case PlanType.TeamsMonthly: + return { tier: "teams", cadence: "monthly" }; + case PlanType.TeamsAnnually: + return { tier: "teams", cadence: "annually" }; + case PlanType.EnterpriseMonthly: + return { tier: "enterprise", cadence: "monthly" }; + case PlanType.EnterpriseAnnually: + return { tier: "enterprise", cadence: "annually" }; + } }; - if (this.organization.useSecretsManager) { - request.secretsManager = { - seats: this.sub.smSeats, - additionalMachineAccounts: - this.sub.smServiceAccounts - this.sub.plan.SecretsManager.baseServiceAccount, - }; - } + const billingAddress = this.billingFormGroup.controls.billingAddress.valid + ? getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress) + : this.billingAddress; - this.taxService - .previewOrganizationInvoice(request) - .then((invoice) => { - this.estimatedTax = invoice.taxAmount; - }) - .catch((error) => { - const translatedMessage = this.i18nService.t(error.message); - this.toastService.showToast({ - title: "", - variant: "error", - message: - !translatedMessage || translatedMessage === "" ? error.message : translatedMessage, - }); - }); + const taxAmounts = await this.taxClient.previewTaxForOrganizationSubscriptionPlanChange( + this.organizationId, + getPlanFromLegacyEnum(this.selectedPlan.type), + billingAddress, + ); + + this.estimatedTax = taxAmounts.tax; } protected canUpdatePaymentInformation(): boolean { return ( this.upgradeRequiresPaymentMethod || this.showPayment || - this.isPaymentSourceEmpty() || + !this.paymentMethod || this.isSubscriptionCanceled ); } @@ -1146,4 +1051,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return this.i18nService.t("upgrade"); } } + + get supportsTaxId() { + return this.formGroup.value.productTier !== ProductTierType.Families; + } + + getCardBrandIcon = () => getCardBrandIcon(this.paymentMethod); } diff --git a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts index 692791db855..5c8df483587 100644 --- a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts @@ -11,7 +11,6 @@ import { WebPlatformUtilsService } from "../../core/web-platform-utils.service"; import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; import { OrganizationSubscriptionCloudComponent } from "./organization-subscription-cloud.component"; import { OrganizationSubscriptionSelfhostComponent } from "./organization-subscription-selfhost.component"; -import { OrganizationPaymentMethodComponent } from "./payment-method/organization-payment-method.component"; const routes: Routes = [ { @@ -26,17 +25,6 @@ const routes: Routes = [ : OrganizationSubscriptionCloudComponent, data: { titleId: "subscription" }, }, - { - path: "payment-method", - component: OrganizationPaymentMethodComponent, - canActivate: [ - organizationPermissionsGuard((org) => org.canEditPaymentMethods), - organizationIsUnmanaged, - ], - data: { - titleId: "paymentMethod", - }, - }, { path: "payment-details", component: OrganizationPaymentDetailsComponent, diff --git a/apps/web/src/app/billing/organizations/organization-billing.module.ts b/apps/web/src/app/billing/organizations/organization-billing.module.ts index 707a854de02..90ba04c4fa4 100644 --- a/apps/web/src/app/billing/organizations/organization-billing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing.module.ts @@ -17,7 +17,6 @@ import { OrganizationBillingRoutingModule } from "./organization-billing-routing import { OrganizationPlansComponent } from "./organization-plans.component"; import { OrganizationSubscriptionCloudComponent } from "./organization-subscription-cloud.component"; import { OrganizationSubscriptionSelfhostComponent } from "./organization-subscription-selfhost.component"; -import { OrganizationPaymentMethodComponent } from "./payment-method/organization-payment-method.component"; import { SecretsManagerAdjustSubscriptionComponent } from "./sm-adjust-subscription.component"; import { SecretsManagerSubscribeStandaloneComponent } from "./sm-subscribe-standalone.component"; import { SubscriptionHiddenComponent } from "./subscription-hidden.component"; @@ -45,7 +44,6 @@ import { SubscriptionStatusComponent } from "./subscription-status.component"; SecretsManagerSubscribeStandaloneComponent, SubscriptionHiddenComponent, SubscriptionStatusComponent, - OrganizationPaymentMethodComponent, ], }) export class OrganizationBillingModule {} diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.html b/apps/web/src/app/billing/organizations/organization-plans.component.html index 3b765927c3c..6234fc6e6e3 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.html +++ b/apps/web/src/app/billing/organizations/organization-plans.component.html @@ -404,17 +404,16 @@

{{ paymentDesc }}

- - - + + } + + > +
{{ "passwordManagerPlanPrice" | i18n }}: {{ passwordManagerSubtotal | currency: "USD $" }} diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 820bee950eb..cbeedc454dc 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -11,10 +11,9 @@ import { } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { Subject, firstValueFrom, takeUntil } from "rxjs"; +import { firstValueFrom, merge, Subject, takeUntil } from "rxjs"; import { debounceTime, map, switchMap } from "rxjs/operators"; -import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { @@ -32,24 +31,12 @@ import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-conso import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { - PaymentMethodType, - PlanSponsorshipType, - PlanType, - ProductTierType, -} from "@bitwarden/common/billing/enums"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; +import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -59,10 +46,20 @@ import { OrgKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +import { + OrganizationSubscriptionPlan, + SubscriberBillingClient, + TaxClient, +} from "@bitwarden/web-vault/app/billing/clients"; +import { + EnterBillingAddressComponent, + EnterPaymentMethodComponent, + getBillingAddressFromForm, +} from "@bitwarden/web-vault/app/billing/payment/components"; +import { tokenizablePaymentMethodToLegacyEnum } from "@bitwarden/web-vault/app/billing/payment/types"; import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module"; import { BillingSharedModule, secretsManagerSubscribeFormFactory } from "../shared"; -import { PaymentComponent } from "../shared/payment/payment.component"; interface OnSuccessArgs { organizationId: string; @@ -78,11 +75,16 @@ const Allowed2020PlansForLegacyProviders = [ @Component({ selector: "app-organization-plans", templateUrl: "organization-plans.component.html", - imports: [BillingSharedModule, OrganizationCreateModule], + imports: [ + BillingSharedModule, + OrganizationCreateModule, + EnterPaymentMethodComponent, + EnterBillingAddressComponent, + ], + providers: [SubscriberBillingClient, TaxClient], }) export class OrganizationPlansComponent implements OnInit, OnDestroy { - @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(ManageTaxInformationComponent) taxComponent: ManageTaxInformationComponent; + @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent!: EnterPaymentMethodComponent; @Input() organizationId?: string; @Input() showFree = true; @@ -105,8 +107,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private _productTier = ProductTierType.Free; - protected taxInformation: TaxInformation; - @Input() get plan(): PlanType { return this._plan; @@ -135,10 +135,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { secretsManagerSubscription = secretsManagerSubscribeFormFactory(this.formBuilder); - selfHostedForm = this.formBuilder.group({ - file: [null, [Validators.required]], - }); - formGroup = this.formBuilder.group({ name: [""], billingEmail: ["", [Validators.email]], @@ -152,6 +148,11 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { secretsManager: this.secretsManagerSubscription, }); + billingFormGroup = this.formBuilder.group({ + paymentMethod: EnterPaymentMethodComponent.getFormGroup(), + billingAddress: EnterBillingAddressComponent.getFormGroup(), + }); + passwordManagerPlans: PlanResponse[]; secretsManagerPlans: PlanResponse[]; organization: Organization; @@ -179,10 +180,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private providerApiService: ProviderApiServiceAbstraction, private toastService: ToastService, - private configService: ConfigService, - private billingApiService: BillingApiServiceAbstraction, - private taxService: TaxServiceAbstraction, private accountService: AccountService, + private subscriberBillingClient: SubscriberBillingClient, + private taxClient: TaxClient, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -199,9 +199,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ); this.billing = await this.organizationApiService.getBilling(this.organizationId); this.sub = await this.organizationApiService.getSubscription(this.organizationId); - this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId); - } else if (!this.selfHosted) { - this.taxInformation = await this.apiService.getTaxInfo(); + const billingAddress = await this.subscriberBillingClient.getBillingAddress({ + type: "organization", + data: this.organization, + }); + this.billingFormGroup.controls.billingAddress.patchValue({ + ...billingAddress, + taxId: billingAddress?.taxId?.value, + }); } if (!this.selfHosted) { @@ -268,15 +273,17 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.loading = false; - this.formGroup.valueChanges.pipe(debounceTime(1000), takeUntil(this.destroy$)).subscribe(() => { - this.refreshSalesTax(); - }); - - this.secretsManagerForm.valueChanges - .pipe(debounceTime(1000), takeUntil(this.destroy$)) - .subscribe(() => { - this.refreshSalesTax(); - }); + merge( + this.formGroup.valueChanges, + this.billingFormGroup.valueChanges, + this.secretsManagerForm.valueChanges, + ) + .pipe( + debounceTime(1000), + switchMap(async () => await this.refreshSalesTax()), + takeUntil(this.destroy$), + ) + .subscribe(); if (this.enableSecretsManagerByDefault && this.selectedSecretsManagerPlan) { this.secretsManagerSubscription.patchValue({ @@ -587,34 +594,13 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.changedProduct(); } - protected changedCountry(): void { - this.paymentComponent.showBankAccount = this.taxInformation?.country === "US"; - if ( - !this.paymentComponent.showBankAccount && - this.paymentComponent.selected === PaymentMethodType.BankAccount - ) { - this.paymentComponent.select(PaymentMethodType.Card); - } - } - - protected onTaxInformationChanged(event: TaxInformation): void { - this.taxInformation = event; - this.changedCountry(); - this.refreshSalesTax(); - } - protected cancel(): void { this.onCanceled.emit(); } - protected setSelectedFile(event: Event): void { - const fileInputEl = event.target; - this.selectedFile = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null; - } - submit = async () => { - if (this.taxComponent && !this.taxComponent.validate()) { - this.taxComponent.markAllAsTouched(); + this.formGroup.markAllAsTouched(); + if (this.formGroup.invalid) { return; } @@ -688,46 +674,54 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } } - private refreshSalesTax(): void { - if (!this.taxComponent.validate()) { + private async refreshSalesTax(): Promise { + if (this.billingFormGroup.controls.billingAddress.invalid) { return; } - const request: PreviewOrganizationInvoiceRequest = { - organizationId: this.organizationId, - passwordManager: { - additionalStorage: this.formGroup.controls.additionalStorage.value, - plan: this.formGroup.controls.plan.value, - sponsoredPlan: this.planSponsorshipType, - seats: this.formGroup.controls.additionalSeats.value, - }, - taxInformation: { - postalCode: this.taxInformation.postalCode, - country: this.taxInformation.country, - taxId: this.taxInformation.taxId, - }, + const getPlanFromLegacyEnum = (): OrganizationSubscriptionPlan => { + switch (this.formGroup.value.plan) { + case PlanType.FamiliesAnnually: + return { tier: "families", cadence: "annually" }; + case PlanType.TeamsMonthly: + return { tier: "teams", cadence: "monthly" }; + case PlanType.TeamsAnnually: + return { tier: "teams", cadence: "annually" }; + case PlanType.EnterpriseMonthly: + return { tier: "enterprise", cadence: "monthly" }; + case PlanType.EnterpriseAnnually: + return { tier: "enterprise", cadence: "annually" }; + } }; - if (this.secretsManagerForm.controls.enabled.value === true) { - request.secretsManager = { - seats: this.secretsManagerForm.controls.userSeats.value, - additionalMachineAccounts: this.secretsManagerForm.controls.additionalServiceAccounts.value, - }; - } + const billingAddress = getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress); - this.taxService - .previewOrganizationInvoice(request) - .then((invoice) => { - this.estimatedTax = invoice.taxAmount; - this.total = invoice.totalAmount; - }) - .catch((error) => { - this.toastService.showToast({ - title: "", - variant: "error", - message: this.i18nService.t(error.message), - }); - }); + const passwordManagerSeats = + this.formGroup.value.productTier === ProductTierType.Families + ? 1 + : this.formGroup.value.additionalSeats; + + const taxAmounts = await this.taxClient.previewTaxForOrganizationSubscriptionPurchase( + { + ...getPlanFromLegacyEnum(), + passwordManager: { + seats: passwordManagerSeats, + additionalStorage: this.formGroup.value.additionalStorage, + sponsored: false, + }, + secretsManager: this.formGroup.value.secretsManager.enabled + ? { + seats: this.secretsManagerForm.value.userSeats, + additionalServiceAccounts: this.secretsManagerForm.value.additionalServiceAccounts, + standalone: false, + } + : undefined, + }, + billingAddress, + ); + + this.estimatedTax = taxAmounts.tax; + this.total = taxAmounts.total; } private async updateOrganization() { @@ -738,21 +732,24 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; - request.billingAddressCountry = this.taxInformation?.country; - request.billingAddressPostalCode = this.taxInformation?.postalCode; + request.billingAddressCountry = this.billingFormGroup.value.billingAddress.country; + request.billingAddressPostalCode = this.billingFormGroup.value.billingAddress.postalCode; // Secrets Manager this.buildSecretsManagerRequest(request); if (this.upgradeRequiresPaymentMethod) { - const updatePaymentMethodRequest = new UpdatePaymentMethodRequest(); - updatePaymentMethodRequest.paymentSource = await this.paymentComponent.tokenize(); - updatePaymentMethodRequest.taxInformation = ExpandedTaxInfoUpdateRequest.From( - this.taxInformation, - ); - await this.billingApiService.updateOrganizationPaymentMethod( - this.organizationId, - updatePaymentMethodRequest, + if (this.billingFormGroup.invalid) { + return; + } + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + await this.subscriberBillingClient.updatePaymentMethod( + { type: "organization", data: this.organization }, + paymentMethod, + { + country: this.billingFormGroup.value.billingAddress.country, + postalCode: this.billingFormGroup.value.billingAddress.postalCode, + }, ); } @@ -791,23 +788,31 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.selectedPlan.type === PlanType.Free) { request.planType = PlanType.Free; } else { - const { type, token } = await this.paymentComponent.tokenize(); + if (this.billingFormGroup.invalid) { + return; + } - request.paymentToken = token; - request.paymentMethodType = type; + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + + const billingAddress = getBillingAddressFromForm( + this.billingFormGroup.controls.billingAddress, + ); + + request.paymentToken = paymentMethod.token; + request.paymentMethodType = tokenizablePaymentMethodToLegacyEnum(paymentMethod.type); request.additionalSeats = this.formGroup.controls.additionalSeats.value; request.additionalStorageGb = this.formGroup.controls.additionalStorage.value; request.premiumAccessAddon = this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; - request.billingAddressPostalCode = this.taxInformation?.postalCode; - request.billingAddressCountry = this.taxInformation?.country; - request.taxIdNumber = this.taxInformation?.taxId; - request.billingAddressLine1 = this.taxInformation?.line1; - request.billingAddressLine2 = this.taxInformation?.line2; - request.billingAddressCity = this.taxInformation?.city; - request.billingAddressState = this.taxInformation?.state; + request.billingAddressPostalCode = billingAddress.postalCode; + request.billingAddressCountry = billingAddress.country; + request.taxIdNumber = billingAddress.taxId?.value; + request.billingAddressLine1 = billingAddress.line1; + request.billingAddressLine2 = billingAddress.line2; + request.billingAddressCity = billingAddress.city; + request.billingAddressState = billingAddress.state; } // Secrets Manager diff --git a/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts b/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts index 47742ba0a88..b2bf27e726a 100644 --- a/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts +++ b/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts @@ -1,15 +1,11 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { BehaviorSubject, - catchError, combineLatest, - EMPTY, filter, firstValueFrom, - from, lastValueFrom, - map, merge, Observable, of, @@ -22,15 +18,13 @@ import { withLatestFrom, } from "rxjs"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { getById } from "@bitwarden/common/platform/misc"; import { DialogService } from "@bitwarden/components"; import { CommandDefinition, MessageListener } from "@bitwarden/messaging"; import { SubscriberBillingClient } from "@bitwarden/web-vault/app/billing/clients"; @@ -54,13 +48,6 @@ import { TaxIdWarningType } from "@bitwarden/web-vault/app/billing/warnings/type import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; -class RedirectError { - constructor( - public path: string[], - public relativeTo: ActivatedRoute, - ) {} -} - type View = { organization: BitwardenSubscriber; paymentMethod: MaskedPaymentMethod | null; @@ -93,24 +80,12 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy { switchMap((userId) => this.organizationService .organizations$(userId) - .pipe(getOrganizationById(this.activatedRoute.snapshot.params.organizationId)), + .pipe(getById(this.activatedRoute.snapshot.params.organizationId)), ), filter((organization): organization is Organization => !!organization), ); private load$: Observable = this.organization$.pipe( - switchMap((organization) => - this.configService - .getFeatureFlag$(FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout) - .pipe( - map((managePaymentDetailsOutsideCheckout) => { - if (!managePaymentDetailsOutsideCheckout) { - throw new RedirectError(["../payment-method"], this.activatedRoute); - } - return organization; - }), - ), - ), mapOrganizationToSubscriber, switchMap(async (organization) => { const getTaxIdWarning = firstValueFrom( @@ -132,14 +107,6 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy { taxIdWarning, }; }), - catchError((error: unknown) => { - if (error instanceof RedirectError) { - return from(this.router.navigate(error.path, { relativeTo: error.relativeTo })).pipe( - switchMap(() => EMPTY), - ); - } - throw error; - }), ); view$: Observable = merge( @@ -159,7 +126,6 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy { private messageListener: MessageListener, private organizationService: OrganizationService, private organizationWarningsService: OrganizationWarningsService, - private router: Router, private subscriberBillingClient: SubscriberBillingClient, ) {} diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html deleted file mode 100644 index ab31147e916..00000000000 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - {{ "loading" | i18n }} - - - - -

- {{ accountCreditHeaderText }} -

-

{{ Math.abs(accountCredit) | currency: "$" }}

-

{{ "creditAppliedDesc" | i18n }}

- -
- - -

{{ "paymentMethod" | i18n }}

-

{{ "noPaymentMethod" | i18n }}

- - - -

- - {{ paymentSource.description }} - - {{ "unverified" | i18n }} -

-
- -

- {{ "paymentChargedWithUnpaidSubscription" | i18n }} -

-
-
-
diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts deleted file mode 100644 index 4106ee4f9cd..00000000000 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, OnDestroy } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, firstValueFrom, from, lastValueFrom, map, switchMap } from "rxjs"; - -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { - OrganizationService, - getOrganizationById, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; -import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; -import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { BillingNotificationService } from "../../services/billing-notification.service"; -import { - AddCreditDialogResult, - openAddCreditDialog, -} from "../../shared/add-credit-dialog.component"; -import { - AdjustPaymentDialogComponent, - AdjustPaymentDialogResultType, -} from "../../shared/adjust-payment-dialog/adjust-payment-dialog.component"; -import { - TRIAL_PAYMENT_METHOD_DIALOG_RESULT_TYPE, - TrialPaymentDialogComponent, -} from "../../shared/trial-payment-dialog/trial-payment-dialog.component"; - -@Component({ - templateUrl: "./organization-payment-method.component.html", - standalone: false, -}) -export class OrganizationPaymentMethodComponent implements OnDestroy { - organizationId!: string; - isUnpaid = false; - accountCredit?: number; - paymentSource?: PaymentSourceResponse; - subscriptionStatus?: string; - organization?: Organization; - organizationSubscriptionResponse?: OrganizationSubscriptionResponse; - - loading = true; - - protected readonly Math = Math; - launchPaymentModalAutomatically = false; - - protected taxInformation?: TaxInformation; - - constructor( - private activatedRoute: ActivatedRoute, - private billingApiService: BillingApiServiceAbstraction, - protected organizationApiService: OrganizationApiServiceAbstraction, - private dialogService: DialogService, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private router: Router, - private toastService: ToastService, - private location: Location, - private organizationService: OrganizationService, - private accountService: AccountService, - protected syncService: SyncService, - private billingNotificationService: BillingNotificationService, - private configService: ConfigService, - ) { - combineLatest([ - this.activatedRoute.params, - this.configService.getFeatureFlag$(FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout), - ]) - .pipe( - switchMap(([{ organizationId }, managePaymentDetailsOutsideCheckout]) => { - if (this.platformUtilsService.isSelfHost()) { - return from(this.router.navigate(["/settings/subscription"])); - } - - if (managePaymentDetailsOutsideCheckout) { - return from( - this.router.navigate(["../payment-details"], { relativeTo: this.activatedRoute }), - ); - } - - this.organizationId = organizationId; - return from(this.load()); - }), - takeUntilDestroyed(), - ) - .subscribe(); - - const state = this.router.getCurrentNavigation()?.extras?.state; - // In case the above state is undefined or null, we use redundantState - const redundantState: any = location.getState(); - const queryParam = this.activatedRoute.snapshot.queryParamMap.get( - "launchPaymentModalAutomatically", - ); - if (state && Object.prototype.hasOwnProperty.call(state, "launchPaymentModalAutomatically")) { - this.launchPaymentModalAutomatically = state.launchPaymentModalAutomatically; - } else if ( - redundantState && - Object.prototype.hasOwnProperty.call(redundantState, "launchPaymentModalAutomatically") - ) { - this.launchPaymentModalAutomatically = redundantState.launchPaymentModalAutomatically; - } else { - this.launchPaymentModalAutomatically = queryParam === "true"; - } - } - ngOnDestroy(): void { - this.launchPaymentModalAutomatically = false; - } - - protected addAccountCredit = async (): Promise => { - if (this.subscriptionStatus === "trialing") { - const hasValidBillingAddress = await this.checkBillingAddressForTrialingOrg(); - if (!hasValidBillingAddress) { - return; - } - } - const dialogRef = openAddCreditDialog(this.dialogService, { - data: { - organizationId: this.organizationId, - }, - }); - - const result = await lastValueFrom(dialogRef.closed); - - if (result === AddCreditDialogResult.Added) { - await this.load(); - } - }; - - protected load = async (): Promise => { - this.loading = true; - try { - const { accountCredit, paymentSource, subscriptionStatus, taxInformation } = - await this.billingApiService.getOrganizationPaymentMethod(this.organizationId); - this.accountCredit = accountCredit; - this.paymentSource = paymentSource; - this.subscriptionStatus = subscriptionStatus; - this.taxInformation = taxInformation; - this.isUnpaid = this.subscriptionStatus === "unpaid"; - - if (this.organizationId) { - const organizationSubscriptionPromise = this.organizationApiService.getSubscription( - this.organizationId, - ); - - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - if (!userId) { - throw new Error("User ID is not found"); - } - - const organizationPromise = await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(this.organizationId)), - ); - - [this.organizationSubscriptionResponse, this.organization] = await Promise.all([ - organizationSubscriptionPromise, - organizationPromise, - ]); - - if (!this.organization) { - throw new Error("Organization is not found"); - } - if (!this.paymentSource) { - throw new Error("Payment source is not found"); - } - } - // If the flag `launchPaymentModalAutomatically` is set to true, - // we schedule a timeout (delay of 800ms) to automatically launch the payment modal. - // This delay ensures that any prior UI/rendering operations complete before triggering the modal. - if (this.launchPaymentModalAutomatically) { - window.setTimeout(async () => { - await this.changePayment(); - this.launchPaymentModalAutomatically = false; - this.location.replaceState(this.location.path(), "", {}); - }, 800); - } - } catch (error) { - this.billingNotificationService.handleError(error); - } finally { - this.loading = false; - } - }; - - protected updatePaymentMethod = async (): Promise => { - const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { - data: { - initialPaymentMethod: this.paymentSource?.type, - organizationId: this.organizationId, - productTier: this.organization?.productTierType, - }, - }); - - const result = await lastValueFrom(dialogRef.closed); - - if (result === AdjustPaymentDialogResultType.Submitted) { - await this.load(); - } - }; - - changePayment = async () => { - const dialogRef = TrialPaymentDialogComponent.open(this.dialogService, { - data: { - organizationId: this.organizationId, - subscription: this.organizationSubscriptionResponse!, - productTierType: this.organization!.productTierType, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === TRIAL_PAYMENT_METHOD_DIALOG_RESULT_TYPE.SUBMITTED) { - this.location.replaceState(this.location.path(), "", {}); - if (this.launchPaymentModalAutomatically && !this.organization?.enabled) { - await this.syncService.fullSync(true); - } - this.launchPaymentModalAutomatically = false; - await this.load(); - } - }; - - protected verifyBankAccount = async (request: VerifyBankAccountRequest): Promise => { - await this.billingApiService.verifyOrganizationBankAccount(this.organizationId, request); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("verifiedBankAccount"), - }); - }; - - protected get accountCreditHeaderText(): string { - const hasAccountCredit = this.accountCredit && this.accountCredit > 0; - const key = hasAccountCredit ? "accountCredit" : "accountBalance"; - return this.i18nService.t(key); - } - - protected get paymentSourceClasses() { - if (this.paymentSource == null) { - return []; - } - switch (this.paymentSource.type) { - case PaymentMethodType.Card: - return ["bwi-credit-card"]; - case PaymentMethodType.BankAccount: - case PaymentMethodType.Check: - return ["bwi-billing"]; - case PaymentMethodType.PayPal: - return ["bwi-paypal text-primary"]; - default: - return []; - } - } - - protected get subscriptionIsUnpaid(): boolean { - return this.subscriptionStatus === "unpaid"; - } - - protected get updatePaymentSourceButtonText(): string { - const key = this.paymentSource == null ? "addPaymentMethod" : "changePaymentMethod"; - return this.i18nService.t(key); - } - - private async checkBillingAddressForTrialingOrg(): Promise { - const hasBillingAddress = this.taxInformation != null; - if (!hasBillingAddress) { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("billingAddressRequiredToAddCredit"), - }); - return false; - } - return true; - } -} diff --git a/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.spec.ts b/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.spec.ts index c7a297cc28b..53f72558089 100644 --- a/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.spec.ts +++ b/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.spec.ts @@ -15,8 +15,6 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogRef, DialogService } from "@bitwarden/components"; import { OrganizationBillingClient } from "@bitwarden/web-vault/app/billing/clients"; @@ -35,7 +33,6 @@ import { TaxIdWarningTypes } from "@bitwarden/web-vault/app/billing/warnings/typ describe("OrganizationWarningsService", () => { let service: OrganizationWarningsService; - let configService: MockProxy; let dialogService: MockProxy; let i18nService: MockProxy; let organizationApiService: MockProxy; @@ -57,7 +54,6 @@ describe("OrganizationWarningsService", () => { }); beforeEach(() => { - configService = mock(); dialogService = mock(); i18nService = mock(); organizationApiService = mock(); @@ -94,7 +90,6 @@ describe("OrganizationWarningsService", () => { TestBed.configureTestingModule({ providers: [ OrganizationWarningsService, - { provide: ConfigService, useValue: configService }, { provide: DialogService, useValue: dialogService }, { provide: I18nService, useValue: i18nService }, { provide: OrganizationApiServiceAbstraction, useValue: organizationApiService }, @@ -466,7 +461,6 @@ describe("OrganizationWarningsService", () => { } as OrganizationWarningsResponse); dialogService.openSimpleDialog.mockResolvedValue(true); - configService.getFeatureFlag.mockResolvedValue(false); router.navigate.mockResolvedValue(true); service.showInactiveSubscriptionDialog$(organization).subscribe({ @@ -478,11 +472,8 @@ describe("OrganizationWarningsService", () => { acceptButtonText: "Continue", cancelButtonText: "Close", }); - expect(configService.getFeatureFlag).toHaveBeenCalledWith( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, - ); expect(router.navigate).toHaveBeenCalledWith( - ["organizations", "org-id-123", "billing", "payment-method"], + ["organizations", "org-id-123", "billing", "payment-details"], { state: { launchPaymentModalAutomatically: true } }, ); done(); @@ -497,7 +488,6 @@ describe("OrganizationWarningsService", () => { } as OrganizationWarningsResponse); dialogService.openSimpleDialog.mockResolvedValue(true); - configService.getFeatureFlag.mockResolvedValue(true); router.navigate.mockResolvedValue(true); service.showInactiveSubscriptionDialog$(organization).subscribe({ @@ -522,7 +512,6 @@ describe("OrganizationWarningsService", () => { service.showInactiveSubscriptionDialog$(organization).subscribe({ complete: () => { expect(dialogService.openSimpleDialog).toHaveBeenCalled(); - expect(configService.getFeatureFlag).not.toHaveBeenCalled(); expect(router.navigate).not.toHaveBeenCalled(); done(); }, diff --git a/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts b/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts index c6bb1bc231b..46a34def28b 100644 --- a/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts +++ b/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts @@ -16,8 +16,6 @@ import { take } from "rxjs/operators"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; @@ -53,7 +51,6 @@ export class OrganizationWarningsService { taxIdWarningRefreshed$ = this.taxIdWarningRefreshedSubject.asObservable(); constructor( - private configService: ConfigService, private dialogService: DialogService, private i18nService: I18nService, private organizationApiService: OrganizationApiServiceAbstraction, @@ -196,14 +193,8 @@ export class OrganizationWarningsService { cancelButtonText: this.i18nService.t("close"), }); if (confirmed) { - const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, - ); - const route = managePaymentDetailsOutsideCheckout - ? "payment-details" - : "payment-method"; await this.router.navigate( - ["organizations", `${organization.id}`, "billing", route], + ["organizations", `${organization.id}`, "billing", "payment-details"], { state: { launchPaymentModalAutomatically: true }, }, diff --git a/apps/web/src/app/billing/payment/components/display-payment-method.component.ts b/apps/web/src/app/billing/payment/components/display-payment-method.component.ts index c33d805aed7..5f5e3442935 100644 --- a/apps/web/src/app/billing/payment/components/display-payment-method.component.ts +++ b/apps/web/src/app/billing/payment/components/display-payment-method.component.ts @@ -5,7 +5,7 @@ import { DialogService } from "@bitwarden/components"; import { SharedModule } from "../../../shared"; import { BitwardenSubscriber } from "../../types"; -import { MaskedPaymentMethod } from "../types"; +import { getCardBrandIcon, MaskedPaymentMethod } from "../types"; import { ChangePaymentMethodDialogComponent } from "./change-payment-method-dialog.component"; @@ -40,9 +40,9 @@ import { ChangePaymentMethodDialogComponent } from "./change-payment-method-dial } @case ("card") {

- @let brandIcon = getBrandIconForCard(); - @if (brandIcon !== null) { - + @let cardBrandIcon = getCardBrandIcon(); + @if (cardBrandIcon !== null) { + } @else { } @@ -74,16 +74,6 @@ export class DisplayPaymentMethodComponent { @Input({ required: true }) paymentMethod!: MaskedPaymentMethod | null; @Output() updated = new EventEmitter(); - protected availableCardIcons: Record = { - amex: "card-amex", - diners: "card-diners-club", - discover: "card-discover", - jcb: "card-jcb", - mastercard: "card-mastercard", - unionpay: "card-unionpay", - visa: "card-visa", - }; - constructor(private dialogService: DialogService) {} changePaymentMethod = async (): Promise => { @@ -100,13 +90,5 @@ export class DisplayPaymentMethodComponent { } }; - protected getBrandIconForCard = (): string | null => { - if (this.paymentMethod?.type !== "card") { - return null; - } - - return this.paymentMethod.brand in this.availableCardIcons - ? this.availableCardIcons[this.paymentMethod.brand] - : null; - }; + protected getCardBrandIcon = () => getCardBrandIcon(this.paymentMethod); } diff --git a/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts b/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts index de2f2f94497..6e356097d32 100644 --- a/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts +++ b/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts @@ -11,10 +11,7 @@ import { ToastService, } from "@bitwarden/components"; import { SubscriberBillingClient } from "@bitwarden/web-vault/app/billing/clients"; -import { - BillingAddress, - getTaxIdTypeForCountry, -} from "@bitwarden/web-vault/app/billing/payment/types"; +import { BillingAddress } from "@bitwarden/web-vault/app/billing/payment/types"; import { BitwardenSubscriber } from "@bitwarden/web-vault/app/billing/types"; import { TaxIdWarningType, @@ -22,7 +19,10 @@ import { } from "@bitwarden/web-vault/app/billing/warnings/types"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; -import { EnterBillingAddressComponent } from "./enter-billing-address.component"; +import { + EnterBillingAddressComponent, + getBillingAddressFromForm, +} from "./enter-billing-address.component"; type DialogParams = { subscriber: BitwardenSubscriber; @@ -104,13 +104,7 @@ export class EditBillingAddressDialogComponent { return; } - const { taxId, ...addressFields } = this.formGroup.getRawValue(); - - const taxIdType = taxId ? getTaxIdTypeForCountry(addressFields.country) : null; - - const billingAddress = taxIdType - ? { ...addressFields, taxId: { code: taxIdType.code, value: taxId! } } - : { ...addressFields, taxId: null }; + const billingAddress = getBillingAddressFromForm(this.formGroup); const result = await this.billingClient.updateBillingAddress( this.dialogParams.subscriber, diff --git a/apps/web/src/app/billing/payment/components/enter-billing-address.component.ts b/apps/web/src/app/billing/payment/components/enter-billing-address.component.ts index 7659b7ed5ca..3f68c12c897 100644 --- a/apps/web/src/app/billing/payment/components/enter-billing-address.component.ts +++ b/apps/web/src/app/billing/payment/components/enter-billing-address.component.ts @@ -24,6 +24,17 @@ export interface BillingAddressControls { export type BillingAddressFormGroup = FormGroup>; +export const getBillingAddressFromForm = (formGroup: BillingAddressFormGroup): BillingAddress => + getBillingAddressFromControls(formGroup.getRawValue()); + +export const getBillingAddressFromControls = (controls: BillingAddressControls) => { + const { taxId, ...addressFields } = controls; + const taxIdType = taxId ? getTaxIdTypeForCountry(addressFields.country) : null; + return taxIdType + ? { ...addressFields, taxId: { code: taxIdType.code, value: taxId! } } + : { ...addressFields, taxId: null }; +}; + type Scenario = | { type: "checkout"; @@ -67,54 +78,56 @@ type Scenario = />

-
- - {{ "address1" | i18n }} - - -
-
- - {{ "address2" | i18n }} - - -
-
- - {{ "cityTown" | i18n }} - - -
-
- - {{ "stateProvince" | i18n }} - - -
+ @if (scenario.type === "update") { +
+ + {{ "address1" | i18n }} + + +
+
+ + {{ "address2" | i18n }} + + +
+
+ + {{ "cityTown" | i18n }} + + +
+
+ + {{ "stateProvince" | i18n }} + + +
+ } @if (supportsTaxId$ | async) {
@@ -175,7 +188,7 @@ export class EnterBillingAddressComponent implements OnInit, OnDestroy { this.supportsTaxId$ = this.group.controls.country.valueChanges.pipe( startWith(this.group.value.country ?? this.selectableCountries[0].value), map((country) => { - if (!this.scenario.supportsTaxId) { + if (!this.scenario.supportsTaxId || country === "US") { return false; } diff --git a/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts b/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts index 93c45b873fe..4af5226e7ee 100644 --- a/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts +++ b/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts @@ -8,7 +8,6 @@ import { PopoverModule, ToastService } from "@bitwarden/components"; import { SharedModule } from "../../../shared"; import { BillingServicesModule, BraintreeService, StripeService } from "../../services"; -import { PaymentLabelComponent } from "../../shared/payment/payment-label.component"; import { isTokenizablePaymentMethod, selectableCountries, @@ -16,6 +15,8 @@ import { TokenizedPaymentMethod, } from "../types"; +import { PaymentLabelComponent } from "./payment-label.component"; + type PaymentMethodOption = TokenizablePaymentMethod | "accountCredit"; type PaymentMethodFormGroup = FormGroup<{ @@ -102,7 +103,7 @@ type PaymentMethodFormGroup = FormGroup<{ - - - - -
- - - - - - - - - - - - - - - -
diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts deleted file mode 100644 index cdf72168acf..00000000000 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ /dev/null @@ -1,191 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, ElementRef, Inject, OnInit, ViewChild } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { firstValueFrom, map } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components"; - -export interface AddCreditDialogData { - organizationId: string; -} - -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum AddCreditDialogResult { - Added = "added", - Cancelled = "cancelled", -} - -export type PayPalConfig = { - businessId?: string; - buttonAction?: string; -}; - -@Component({ - templateUrl: "add-credit-dialog.component.html", - standalone: false, -}) -export class AddCreditDialogComponent implements OnInit { - @ViewChild("ppButtonForm", { read: ElementRef, static: true }) ppButtonFormRef: ElementRef; - - paymentMethodType = PaymentMethodType; - ppButtonFormAction: string; - ppButtonBusinessId: string; - ppButtonCustomField: string; - ppLoading = false; - subject: string; - returnUrl: string; - organizationId: string; - - private userId: string; - private name: string; - private email: string; - private region: string; - - protected DialogResult = AddCreditDialogResult; - protected formGroup = new FormGroup({ - method: new FormControl(PaymentMethodType.PayPal), - creditAmount: new FormControl(null, [Validators.required]), - }); - - constructor( - private dialogRef: DialogRef, - @Inject(DIALOG_DATA) protected data: AddCreditDialogData, - private accountService: AccountService, - private apiService: ApiService, - private platformUtilsService: PlatformUtilsService, - private organizationService: OrganizationService, - private logService: LogService, - private configService: ConfigService, - ) { - this.organizationId = data.organizationId; - const payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig; - this.ppButtonFormAction = payPalConfig.buttonAction; - this.ppButtonBusinessId = payPalConfig.businessId; - } - - async ngOnInit() { - if (this.organizationId != null) { - if (this.creditAmount == null) { - this.creditAmount = "0.00"; - } - this.ppButtonCustomField = "organization_id:" + this.organizationId; - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const org = await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(this.organizationId)), - ); - if (org != null) { - this.subject = org.name; - this.name = org.name; - } - } else { - if (this.creditAmount == null) { - this.creditAmount = "0.00"; - } - const [userId, email] = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), - ); - this.userId = userId; - this.subject = email; - this.email = this.subject; - this.ppButtonCustomField = "user_id:" + this.userId; - } - this.region = await firstValueFrom(this.configService.cloudRegion$); - this.ppButtonCustomField += ",account_credit:1"; - this.ppButtonCustomField += `,region:${this.region}`; - this.returnUrl = window.location.href; - } - - get creditAmount() { - return this.formGroup.value.creditAmount; - } - set creditAmount(value: string) { - this.formGroup.get("creditAmount").setValue(value); - } - - get method() { - return this.formGroup.value.method; - } - - submit = async () => { - if (this.creditAmount == null || this.creditAmount === "") { - return; - } - - if (this.method === PaymentMethodType.PayPal) { - this.ppButtonFormRef.nativeElement.submit(); - this.ppLoading = true; - return; - } - if (this.method === PaymentMethodType.BitPay) { - const req = new BitPayInvoiceRequest(); - req.email = this.email; - req.name = this.name; - req.credit = true; - req.amount = this.creditAmountNumber; - req.organizationId = this.organizationId; - req.userId = this.userId; - req.returnUrl = this.returnUrl; - const bitPayUrl: string = await this.apiService.postBitPayInvoice(req); - this.platformUtilsService.launchUri(bitPayUrl); - return; - } - this.dialogRef.close(AddCreditDialogResult.Added); - }; - - formatAmount() { - try { - if (this.creditAmount != null && this.creditAmount !== "") { - const floatAmount = Math.abs(parseFloat(this.creditAmount)); - if (floatAmount > 0) { - this.creditAmount = parseFloat((Math.round(floatAmount * 100) / 100).toString()) - .toFixed(2) - .toString(); - return; - } - } - } catch (e) { - this.logService.error(e); - } - this.creditAmount = ""; - } - - get creditAmountNumber(): number { - if (this.creditAmount != null && this.creditAmount !== "") { - try { - return parseFloat(this.creditAmount); - } catch (e) { - this.logService.error(e); - } - } - return null; - } -} - -/** - * Strongly typed helper to open a AddCreditDialog - * @param dialogService Instance of the dialog service that will be used to open the dialog - * @param config Configuration for the dialog - */ -export function openAddCreditDialog( - dialogService: DialogService, - config: DialogConfig, -) { - return dialogService.open(AddCreditDialogComponent, config); -} diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html deleted file mode 100644 index 9c70908af8e..00000000000 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts deleted file mode 100644 index 9944085488f..00000000000 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts +++ /dev/null @@ -1,225 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, forwardRef, Inject, OnInit, ViewChild } from "@angular/core"; - -import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { PaymentMethodType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; -import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { - DIALOG_DATA, - DialogConfig, - DialogRef, - DialogService, - ToastService, -} from "@bitwarden/components"; - -import { PaymentComponent } from "../payment/payment.component"; - -export interface AdjustPaymentDialogParams { - initialPaymentMethod?: PaymentMethodType | null; - organizationId?: string; - productTier?: ProductTierType; - providerId?: string; -} - -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum AdjustPaymentDialogResultType { - Closed = "closed", - Submitted = "submitted", -} - -@Component({ - templateUrl: "./adjust-payment-dialog.component.html", - standalone: false, -}) -export class AdjustPaymentDialogComponent implements OnInit { - @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(forwardRef(() => ManageTaxInformationComponent)) - taxInfoComponent: ManageTaxInformationComponent; - - protected readonly PaymentMethodType = PaymentMethodType; - protected readonly ResultType = AdjustPaymentDialogResultType; - - protected dialogHeader: string; - protected initialPaymentMethod: PaymentMethodType; - protected organizationId?: string; - protected productTier?: ProductTierType; - protected providerId?: string; - - protected loading = true; - - protected taxInformation: TaxInformation; - - constructor( - private apiService: ApiService, - private billingApiService: BillingApiServiceAbstraction, - private organizationApiService: OrganizationApiServiceAbstraction, - @Inject(DIALOG_DATA) protected dialogParams: AdjustPaymentDialogParams, - private dialogRef: DialogRef, - private i18nService: I18nService, - private toastService: ToastService, - ) { - const key = this.dialogParams.initialPaymentMethod ? "changePaymentMethod" : "addPaymentMethod"; - this.dialogHeader = this.i18nService.t(key); - this.initialPaymentMethod = this.dialogParams.initialPaymentMethod ?? PaymentMethodType.Card; - this.organizationId = this.dialogParams.organizationId; - this.productTier = this.dialogParams.productTier; - this.providerId = this.dialogParams.providerId; - } - - ngOnInit(): void { - if (this.organizationId) { - this.organizationApiService - .getTaxInfo(this.organizationId) - .then((response: TaxInfoResponse) => { - this.taxInformation = TaxInformation.from(response); - this.toggleBankAccount(); - }) - .catch(() => { - this.taxInformation = new TaxInformation(); - }) - .finally(() => { - this.loading = false; - }); - } else if (this.providerId) { - this.billingApiService - .getProviderTaxInformation(this.providerId) - .then((response) => { - this.taxInformation = TaxInformation.from(response); - this.toggleBankAccount(); - }) - .catch(() => { - this.taxInformation = new TaxInformation(); - }) - .finally(() => { - this.loading = false; - }); - } else { - this.apiService - .getTaxInfo() - .then((response: TaxInfoResponse) => { - this.taxInformation = TaxInformation.from(response); - }) - .catch(() => { - this.taxInformation = new TaxInformation(); - }) - .finally(() => { - this.loading = false; - }); - } - } - - taxInformationChanged(event: TaxInformation) { - this.taxInformation = event; - this.toggleBankAccount(); - } - - toggleBankAccount = () => { - if (this.taxInformation.country === "US") { - this.paymentComponent.showBankAccount = !!this.organizationId || !!this.providerId; - } else { - this.paymentComponent.showBankAccount = false; - if (this.paymentComponent.selected === PaymentMethodType.BankAccount) { - this.paymentComponent.select(PaymentMethodType.Card); - } - } - }; - - submit = async (): Promise => { - if (!this.taxInfoComponent.validate()) { - this.taxInfoComponent.markAllAsTouched(); - return; - } - - try { - if (this.organizationId) { - await this.updateOrganizationPaymentMethod(); - } else if (this.providerId) { - await this.updateProviderPaymentMethod(); - } else { - await this.updatePremiumUserPaymentMethod(); - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("updatedPaymentMethod"), - }); - - this.dialogRef.close(AdjustPaymentDialogResultType.Submitted); - } catch (error) { - const msg = typeof error == "object" ? error.message : error; - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t(msg) || msg, - }); - } - }; - - private updateOrganizationPaymentMethod = async () => { - const paymentSource = await this.paymentComponent.tokenize(); - - const request = new UpdatePaymentMethodRequest(); - request.paymentSource = paymentSource; - request.taxInformation = ExpandedTaxInfoUpdateRequest.From(this.taxInformation); - - await this.billingApiService.updateOrganizationPaymentMethod(this.organizationId, request); - }; - - private updatePremiumUserPaymentMethod = async () => { - const { type, token } = await this.paymentComponent.tokenize(); - - const request = new PaymentRequest(); - request.paymentMethodType = type; - request.paymentToken = token; - request.country = this.taxInformation.country; - request.postalCode = this.taxInformation.postalCode; - request.taxId = this.taxInformation.taxId; - request.state = this.taxInformation.state; - request.line1 = this.taxInformation.line1; - request.line2 = this.taxInformation.line2; - request.city = this.taxInformation.city; - request.state = this.taxInformation.state; - await this.apiService.postAccountPayment(request); - }; - - private updateProviderPaymentMethod = async () => { - const paymentSource = await this.paymentComponent.tokenize(); - - const request = new UpdatePaymentMethodRequest(); - request.paymentSource = paymentSource; - request.taxInformation = ExpandedTaxInfoUpdateRequest.From(this.taxInformation); - - await this.billingApiService.updateProviderPaymentMethod(this.providerId, request); - }; - - protected get showTaxIdField(): boolean { - if (this.organizationId) { - switch (this.productTier) { - case ProductTierType.Free: - case ProductTierType.Families: - return false; - default: - return true; - } - } else { - return !!this.providerId; - } - } - - static open = ( - dialogService: DialogService, - dialogConfig: DialogConfig, - ) => - dialogService.open(AdjustPaymentDialogComponent, dialogConfig); -} diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index 7322f047551..fb593b39328 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -1,46 +1,40 @@ import { NgModule } from "@angular/core"; import { BannerModule } from "@bitwarden/components"; +import { + EnterBillingAddressComponent, + EnterPaymentMethodComponent, +} from "@bitwarden/web-vault/app/billing/payment/components"; import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; -import { AddCreditDialogComponent } from "./add-credit-dialog.component"; -import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog/adjust-payment-dialog.component"; import { AdjustStorageDialogComponent } from "./adjust-storage-dialog/adjust-storage-dialog.component"; import { BillingHistoryComponent } from "./billing-history.component"; import { OffboardingSurveyComponent } from "./offboarding-survey.component"; -import { PaymentComponent } from "./payment/payment.component"; -import { PaymentMethodComponent } from "./payment-method.component"; import { PlanCardComponent } from "./plan-card/plan-card.component"; import { PricingSummaryComponent } from "./pricing-summary/pricing-summary.component"; import { IndividualSelfHostingLicenseUploaderComponent } from "./self-hosting-license-uploader/individual-self-hosting-license-uploader.component"; import { OrganizationSelfHostingLicenseUploaderComponent } from "./self-hosting-license-uploader/organization-self-hosting-license-uploader.component"; import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component"; -import { TaxInfoComponent } from "./tax-info.component"; import { TrialPaymentDialogComponent } from "./trial-payment-dialog/trial-payment-dialog.component"; import { UpdateLicenseDialogComponent } from "./update-license-dialog.component"; import { UpdateLicenseComponent } from "./update-license.component"; -import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-account.component"; @NgModule({ imports: [ SharedModule, - TaxInfoComponent, HeaderModule, BannerModule, - PaymentComponent, - VerifyBankAccountComponent, + EnterPaymentMethodComponent, + EnterBillingAddressComponent, ], declarations: [ - AddCreditDialogComponent, BillingHistoryComponent, - PaymentMethodComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, UpdateLicenseDialogComponent, OffboardingSurveyComponent, - AdjustPaymentDialogComponent, AdjustStorageDialogComponent, IndividualSelfHostingLicenseUploaderComponent, OrganizationSelfHostingLicenseUploaderComponent, @@ -50,14 +44,11 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac ], exports: [ SharedModule, - TaxInfoComponent, BillingHistoryComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, UpdateLicenseDialogComponent, OffboardingSurveyComponent, - VerifyBankAccountComponent, - PaymentComponent, IndividualSelfHostingLicenseUploaderComponent, OrganizationSelfHostingLicenseUploaderComponent, ], diff --git a/apps/web/src/app/billing/shared/index.ts b/apps/web/src/app/billing/shared/index.ts index 54ab5bc0a2a..466d1d3e586 100644 --- a/apps/web/src/app/billing/shared/index.ts +++ b/apps/web/src/app/billing/shared/index.ts @@ -1,4 +1,2 @@ export * from "./billing-shared.module"; -export * from "./payment-method.component"; export * from "./sm-subscribe.component"; -export * from "./tax-info.component"; diff --git a/apps/web/src/app/billing/shared/payment-method.component.html b/apps/web/src/app/billing/shared/payment-method.component.html deleted file mode 100644 index 81ed7e5a631..00000000000 --- a/apps/web/src/app/billing/shared/payment-method.component.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -

{{ "paymentMethod" | i18n }}

- - - - {{ "loading" | i18n }} - - - -

- {{ (isCreditBalance ? "accountCredit" : "accountBalance") | i18n }} -

-

{{ creditOrBalance | currency: "$" }}

-

{{ "creditAppliedDesc" | i18n }}

- -
- -

{{ "paymentMethod" | i18n }}

-

{{ "noPaymentMethod" | i18n }}

- - -

- {{ "verifyBankAccountDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} -

-
- - {{ "amountX" | i18n: "1" }} - - $0. - - - {{ "amountX" | i18n: "2" }} - - $0. - - -
-
-

- - {{ paymentSource.description }} -

-
- -

- {{ "paymentChargedWithUnpaidSubscription" | i18n }} -

-
-
-
diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts deleted file mode 100644 index b6431843b83..00000000000 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { Location } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { FormBuilder, FormControl, Validators } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, lastValueFrom, map } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { - OrganizationService, - getOrganizationById, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { BillingPaymentResponse } from "@bitwarden/common/billing/models/response/billing-payment.response"; -import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; -import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component"; -import { - AdjustPaymentDialogComponent, - AdjustPaymentDialogResultType, -} from "./adjust-payment-dialog/adjust-payment-dialog.component"; - -@Component({ - templateUrl: "payment-method.component.html", - standalone: false, -}) -export class PaymentMethodComponent implements OnInit, OnDestroy { - loading = false; - firstLoaded = false; - billing?: BillingPaymentResponse; - org?: OrganizationSubscriptionResponse; - sub?: SubscriptionResponse; - paymentMethodType = PaymentMethodType; - organizationId?: string; - isUnpaid = false; - organization?: Organization; - - verifyBankForm = this.formBuilder.group({ - amount1: new FormControl(0, [ - Validators.required, - Validators.max(99), - Validators.min(0), - ]), - amount2: new FormControl(0, [ - Validators.required, - Validators.max(99), - Validators.min(0), - ]), - }); - - launchPaymentModalAutomatically = false; - constructor( - protected apiService: ApiService, - protected organizationApiService: OrganizationApiServiceAbstraction, - protected i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, - private router: Router, - private location: Location, - private route: ActivatedRoute, - private formBuilder: FormBuilder, - private dialogService: DialogService, - private toastService: ToastService, - private organizationService: OrganizationService, - private accountService: AccountService, - protected syncService: SyncService, - private configService: ConfigService, - ) { - const state = this.router.getCurrentNavigation()?.extras?.state; - // In case the above state is undefined or null, we use redundantState - const redundantState: any = location.getState(); - if (state && Object.prototype.hasOwnProperty.call(state, "launchPaymentModalAutomatically")) { - this.launchPaymentModalAutomatically = state.launchPaymentModalAutomatically; - } else if ( - redundantState && - Object.prototype.hasOwnProperty.call(redundantState, "launchPaymentModalAutomatically") - ) { - this.launchPaymentModalAutomatically = redundantState.launchPaymentModalAutomatically; - } else { - this.launchPaymentModalAutomatically = false; - } - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.params.subscribe(async (params) => { - if (params.organizationId) { - this.organizationId = params.organizationId; - } else if (this.platformUtilsService.isSelfHost()) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/settings/subscription"]); - return; - } - - const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, - ); - - if (managePaymentDetailsOutsideCheckout) { - await this.router.navigate(["../payment-details"], { relativeTo: this.route }); - } - - await this.load(); - this.firstLoaded = true; - }); - } - - load = async () => { - if (this.loading) { - return; - } - this.loading = true; - if (this.forOrganization) { - const billingPromise = this.organizationApiService.getBilling(this.organizationId!); - const organizationSubscriptionPromise = this.organizationApiService.getSubscription( - this.organizationId!, - ); - - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - if (!userId) { - throw new Error("User ID is not found"); - } - - const organizationPromise = await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(this.organizationId!)), - ); - - [this.billing, this.org, this.organization] = await Promise.all([ - billingPromise, - organizationSubscriptionPromise, - organizationPromise, - ]); - } else { - const billingPromise = this.apiService.getUserBillingPayment(); - const subPromise = this.apiService.getUserSubscription(); - - [this.billing, this.sub] = await Promise.all([billingPromise, subPromise]); - } - // TODO: Eslint upgrade. Please resolve this since the ?? does nothing - // eslint-disable-next-line no-constant-binary-expression - this.isUnpaid = this.subscription?.status === "unpaid" ?? false; - this.loading = false; - // If the flag `launchPaymentModalAutomatically` is set to true, - // we schedule a timeout (delay of 800ms) to automatically launch the payment modal. - // This delay ensures that any prior UI/rendering operations complete before triggering the modal. - if (this.launchPaymentModalAutomatically) { - window.setTimeout(async () => { - await this.changePayment(); - this.launchPaymentModalAutomatically = false; - this.location.replaceState(this.location.path(), "", {}); - }, 800); - } - }; - - addCredit = async () => { - if (this.forOrganization) { - const dialogRef = openAddCreditDialog(this.dialogService, { - data: { - organizationId: this.organizationId!, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AddCreditDialogResult.Added) { - await this.load(); - } - } - }; - - changePayment = async () => { - const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { - data: { - organizationId: this.organizationId, - initialPaymentMethod: this.paymentSource !== null ? this.paymentSource.type : null, - }, - }); - - const result = await lastValueFrom(dialogRef.closed); - - if (result === AdjustPaymentDialogResultType.Submitted) { - this.location.replaceState(this.location.path(), "", {}); - if (this.launchPaymentModalAutomatically && !this.organization?.enabled) { - await this.syncService.fullSync(true); - } - this.launchPaymentModalAutomatically = false; - await this.load(); - } - }; - - verifyBank = async () => { - if (this.loading || !this.forOrganization) { - return; - } - - const request = new VerifyBankRequest(); - request.amount1 = this.verifyBankForm.value.amount1!; - request.amount2 = this.verifyBankForm.value.amount2!; - await this.organizationApiService.verifyBank(this.organizationId!, request); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("verifiedBankAccount"), - }); - await this.load(); - }; - - get isCreditBalance() { - return this.billing == null || this.billing.balance <= 0; - } - - get creditOrBalance() { - return Math.abs(this.billing != null ? this.billing.balance : 0); - } - - get paymentSource() { - return this.billing != null ? this.billing.paymentSource : null; - } - - get forOrganization() { - return this.organizationId != null; - } - - get paymentSourceClasses() { - if (this.paymentSource == null) { - return []; - } - switch (this.paymentSource.type) { - case PaymentMethodType.Card: - return ["bwi-credit-card"]; - case PaymentMethodType.BankAccount: - case PaymentMethodType.Check: - return ["bwi-billing"]; - case PaymentMethodType.PayPal: - return ["bwi-paypal text-primary"]; - default: - return []; - } - } - - get subscription() { - return this.sub?.subscription ?? this.org?.subscription ?? null; - } - - ngOnDestroy(): void { - this.launchPaymentModalAutomatically = false; - } -} diff --git a/apps/web/src/app/billing/shared/payment/payment-label.component.html b/apps/web/src/app/billing/shared/payment/payment-label.component.html deleted file mode 100644 index a931b0524e3..00000000000 --- a/apps/web/src/app/billing/shared/payment/payment-label.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - -
- - - ({{ "required" | i18n }}) - -
diff --git a/apps/web/src/app/billing/shared/payment/payment.component.html b/apps/web/src/app/billing/shared/payment/payment.component.html deleted file mode 100644 index d1356c20854..00000000000 --- a/apps/web/src/app/billing/shared/payment/payment.component.html +++ /dev/null @@ -1,149 +0,0 @@ -
-
- - - - - {{ "creditCard" | i18n }} - - - - - - {{ "bankAccount" | i18n }} - - - - - - {{ "payPal" | i18n }} - - - - - - {{ "accountCredit" | i18n }} - - - -
- - -
-
- - {{ "number" | i18n }} - -
-
-
- Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay -
-
- - {{ "expiration" | i18n }} - -
-
-
- - {{ "securityCodeSlashCVV" | i18n }} - - - - -
-
-
-
- - - - {{ "requiredToVerifyBankAccountWithStripe" | i18n }} - -
- - {{ "routingNumber" | i18n }} - - - - {{ "accountNumber" | i18n }} - - - - {{ "accountHolderName" | i18n }} - - - - {{ "bankAccountType" | i18n }} - - - - - - -
-
- - -
-
- {{ "paypalClickSubmit" | i18n }} -
-
- - - - {{ "makeSureEnoughCredit" | i18n }} - - - -
diff --git a/apps/web/src/app/billing/shared/payment/payment.component.ts b/apps/web/src/app/billing/shared/payment/payment.component.ts deleted file mode 100644 index 08476e9952f..00000000000 --- a/apps/web/src/app/billing/shared/payment/payment.component.ts +++ /dev/null @@ -1,215 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { Subject } from "rxjs"; -import { takeUntil } from "rxjs/operators"; - -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -import { SharedModule } from "../../../shared"; -import { BillingServicesModule, BraintreeService, StripeService } from "../../services"; - -import { PaymentLabelComponent } from "./payment-label.component"; - -/** - * Render a form that allows the user to enter their payment method, tokenize it against one of our payment providers and, - * optionally, submit it using the {@link onSubmit} function if it is provided. - */ -@Component({ - selector: "app-payment", - templateUrl: "./payment.component.html", - imports: [BillingServicesModule, SharedModule, PaymentLabelComponent], -}) -export class PaymentComponent implements OnInit, OnDestroy { - /** Show account credit as a payment option. */ - @Input() showAccountCredit: boolean = true; - /** Show bank account as a payment option. */ - @Input() showBankAccount: boolean = true; - /** Show PayPal as a payment option. */ - @Input() showPayPal: boolean = true; - - /** The payment method selected by default when the component renders. */ - @Input() private initialPaymentMethod: PaymentMethodType = PaymentMethodType.Card; - /** If provided, will be invoked with the tokenized payment source during form submission. */ - @Input() protected onSubmit?: (request: TokenizedPaymentSourceRequest) => Promise; - - @Input() private bankAccountWarningOverride?: string; - - @Output() submitted = new EventEmitter(); - - private destroy$ = new Subject(); - - protected formGroup = new FormGroup({ - paymentMethod: new FormControl(null), - bankInformation: new FormGroup({ - routingNumber: new FormControl("", [Validators.required]), - accountNumber: new FormControl("", [Validators.required]), - accountHolderName: new FormControl("", [Validators.required]), - accountHolderType: new FormControl("", [Validators.required]), - }), - }); - - protected PaymentMethodType = PaymentMethodType; - - constructor( - private billingApiService: BillingApiServiceAbstraction, - private braintreeService: BraintreeService, - private i18nService: I18nService, - private stripeService: StripeService, - ) {} - - ngOnInit(): void { - this.formGroup.controls.paymentMethod.patchValue(this.initialPaymentMethod); - - this.stripeService.loadStripe( - { - cardNumber: "#stripe-card-number", - cardExpiry: "#stripe-card-expiry", - cardCvc: "#stripe-card-cvc", - }, - this.initialPaymentMethod === PaymentMethodType.Card, - ); - - if (this.showPayPal) { - this.braintreeService.loadBraintree( - "#braintree-container", - this.initialPaymentMethod === PaymentMethodType.PayPal, - ); - } - - this.formGroup - .get("paymentMethod") - .valueChanges.pipe(takeUntil(this.destroy$)) - .subscribe((type) => { - this.onPaymentMethodChange(type); - }); - } - - /** Programmatically select the provided payment method. */ - select = (paymentMethod: PaymentMethodType) => { - this.formGroup.get("paymentMethod").patchValue(paymentMethod); - }; - - protected submit = async () => { - const { type, token } = await this.tokenize(); - await this.onSubmit?.({ type, token }); - this.submitted.emit(type); - }; - - validate = () => { - if (!this.usingBankAccount) { - return true; - } - - this.formGroup.controls.bankInformation.markAllAsTouched(); - return this.formGroup.controls.bankInformation.valid; - }; - - /** - * Tokenize the payment method information entered by the user against one of our payment providers. - * - * - {@link PaymentMethodType.Card} => [Stripe.confirmCardSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_card_setup} - * - {@link PaymentMethodType.BankAccount} => [Stripe.confirmUsBankAccountSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_us_bank_account_setup} - * - {@link PaymentMethodType.PayPal} => [Braintree.requestPaymentMethod]{@link https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html#requestPaymentMethod} - * */ - async tokenize(): Promise<{ type: PaymentMethodType; token: string }> { - const type = this.selected; - - if (this.usingStripe) { - const clientSecret = await this.billingApiService.createSetupIntent(type); - - if (this.usingBankAccount) { - this.formGroup.markAllAsTouched(); - if (this.formGroup.valid) { - const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, { - accountHolderName: this.formGroup.value.bankInformation.accountHolderName, - routingNumber: this.formGroup.value.bankInformation.routingNumber, - accountNumber: this.formGroup.value.bankInformation.accountNumber, - accountHolderType: this.formGroup.value.bankInformation.accountHolderType, - }); - return { - type, - token, - }; - } else { - throw "Invalid input provided. Please ensure all required fields are filled out correctly and try again."; - } - } - - if (this.usingCard) { - const token = await this.stripeService.setupCardPaymentMethod(clientSecret); - return { - type, - token, - }; - } - } - - if (this.usingPayPal) { - const token = await this.braintreeService.requestPaymentMethod(); - return { - type, - token, - }; - } - - if (this.usingAccountCredit) { - return { - type: PaymentMethodType.Credit, - token: null, - }; - } - - return null; - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - this.stripeService.unloadStripe(); - if (this.showPayPal) { - this.braintreeService.unloadBraintree(); - } - } - - private onPaymentMethodChange(type: PaymentMethodType): void { - switch (type) { - case PaymentMethodType.Card: { - this.stripeService.mountElements(); - break; - } - case PaymentMethodType.PayPal: { - this.braintreeService.createDropin(); - break; - } - } - } - - get selected(): PaymentMethodType { - return this.formGroup.value.paymentMethod; - } - - protected get usingAccountCredit(): boolean { - return this.selected === PaymentMethodType.Credit; - } - - protected get usingBankAccount(): boolean { - return this.selected === PaymentMethodType.BankAccount; - } - - protected get usingCard(): boolean { - return this.selected === PaymentMethodType.Card; - } - - protected get usingPayPal(): boolean { - return this.selected === PaymentMethodType.PayPal; - } - - private get usingStripe(): boolean { - return this.usingBankAccount || this.usingCard; - } -} diff --git a/apps/web/src/app/billing/shared/tax-info.component.html b/apps/web/src/app/billing/shared/tax-info.component.html deleted file mode 100644 index ca2ae046f6e..00000000000 --- a/apps/web/src/app/billing/shared/tax-info.component.html +++ /dev/null @@ -1,83 +0,0 @@ -
-
-
- - {{ "country" | i18n }} - - - - -
-
- - {{ "zipPostalCode" | i18n }} - - -
-
- - {{ "address1" | i18n }} - - -
-
- - {{ "address2" | i18n }} - - -
-
- - {{ "cityTown" | i18n }} - - -
-
- - {{ "stateProvince" | i18n }} - - -
-
- - {{ "taxIdNumber" | i18n }} - - -
-
-
diff --git a/apps/web/src/app/billing/shared/tax-info.component.ts b/apps/web/src/app/billing/shared/tax-info.component.ts deleted file mode 100644 index 35c4a3fcc4e..00000000000 --- a/apps/web/src/app/billing/shared/tax-info.component.ts +++ /dev/null @@ -1,199 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { ActivatedRoute } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; -import { debounceTime } from "rxjs/operators"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { CountryListItem } from "@bitwarden/common/billing/models/domain"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; - -import { SharedModule } from "../../shared"; - -/** - * @deprecated Use `ManageTaxInformationComponent` instead. - */ -@Component({ - selector: "app-tax-info", - templateUrl: "tax-info.component.html", - imports: [SharedModule], -}) -export class TaxInfoComponent implements OnInit, OnDestroy { - private destroy$ = new Subject(); - - @Input() trialFlow = false; - @Output() countryChanged = new EventEmitter(); - @Output() taxInformationChanged: EventEmitter = new EventEmitter(); - - taxFormGroup = new FormGroup({ - country: new FormControl(null, [Validators.required]), - postalCode: new FormControl(null, [Validators.required]), - taxId: new FormControl(null), - line1: new FormControl(null), - line2: new FormControl(null), - city: new FormControl(null), - state: new FormControl(null), - }); - - protected isTaxSupported: boolean; - - loading = true; - organizationId: string; - providerId: string; - countryList: CountryListItem[] = this.taxService.getCountries(); - - constructor( - private apiService: ApiService, - private route: ActivatedRoute, - private logService: LogService, - private organizationApiService: OrganizationApiServiceAbstraction, - private taxService: TaxServiceAbstraction, - ) {} - - get country(): string { - return this.taxFormGroup.controls.country.value; - } - - get postalCode(): string { - return this.taxFormGroup.controls.postalCode.value; - } - - get taxId(): string { - return this.taxFormGroup.controls.taxId.value; - } - - get line1(): string { - return this.taxFormGroup.controls.line1.value; - } - - get line2(): string { - return this.taxFormGroup.controls.line2.value; - } - - get city(): string { - return this.taxFormGroup.controls.city.value; - } - - get state(): string { - return this.taxFormGroup.controls.state.value; - } - - get showTaxIdField(): boolean { - return !!this.organizationId; - } - - async ngOnInit() { - // Provider setup - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.route.queryParams.subscribe((params) => { - this.providerId = params.providerId; - }); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent?.parent?.params.subscribe(async (params) => { - this.organizationId = params.organizationId; - if (this.organizationId) { - try { - const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); - if (taxInfo) { - this.taxFormGroup.controls.taxId.setValue(taxInfo.taxId); - this.taxFormGroup.controls.state.setValue(taxInfo.state); - this.taxFormGroup.controls.line1.setValue(taxInfo.line1); - this.taxFormGroup.controls.line2.setValue(taxInfo.line2); - this.taxFormGroup.controls.city.setValue(taxInfo.city); - this.taxFormGroup.controls.postalCode.setValue(taxInfo.postalCode); - this.taxFormGroup.controls.country.setValue(taxInfo.country); - } - } catch (e) { - this.logService.error(e); - } - } else { - try { - const taxInfo = await this.apiService.getTaxInfo(); - if (taxInfo) { - this.taxFormGroup.controls.postalCode.setValue(taxInfo.postalCode); - this.taxFormGroup.controls.country.setValue(taxInfo.country); - } - } catch (e) { - this.logService.error(e); - } - } - - this.isTaxSupported = await this.taxService.isCountrySupported( - this.taxFormGroup.controls.country.value, - ); - - this.countryChanged.emit(); - }); - - this.taxFormGroup.controls.country.valueChanges - .pipe(debounceTime(1000), takeUntil(this.destroy$)) - .subscribe((value) => { - this.taxService - .isCountrySupported(this.taxFormGroup.controls.country.value) - .then((isSupported) => { - this.isTaxSupported = isSupported; - }) - .catch(() => { - this.isTaxSupported = false; - }) - .finally(() => { - if (!this.isTaxSupported) { - this.taxFormGroup.controls.taxId.setValue(null); - this.taxFormGroup.controls.line1.setValue(null); - this.taxFormGroup.controls.line2.setValue(null); - this.taxFormGroup.controls.city.setValue(null); - this.taxFormGroup.controls.state.setValue(null); - } - - this.countryChanged.emit(); - }); - this.taxInformationChanged.emit(); - }); - - this.taxFormGroup.controls.postalCode.valueChanges - .pipe(debounceTime(1000), takeUntil(this.destroy$)) - .subscribe(() => { - this.taxInformationChanged.emit(); - }); - - this.taxFormGroup.controls.taxId.valueChanges - .pipe(debounceTime(1000), takeUntil(this.destroy$)) - .subscribe(() => { - this.taxInformationChanged.emit(); - }); - - this.loading = false; - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - submitTaxInfo(): Promise { - this.taxFormGroup.updateValueAndValidity(); - this.taxFormGroup.markAllAsTouched(); - - const request = new ExpandedTaxInfoUpdateRequest(); - request.country = this.country; - request.postalCode = this.postalCode; - request.taxId = this.taxId; - request.line1 = this.line1; - request.line2 = this.line2; - request.city = this.city; - request.state = this.state; - - return this.organizationId - ? this.organizationApiService.updateTaxInfo( - this.organizationId, - request as ExpandedTaxInfoUpdateRequest, - ) - : this.apiService.putTaxInfo(request); - } -} diff --git a/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html b/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html index dbd2899c9e0..1b416eae1bc 100644 --- a/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html +++ b/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html @@ -86,17 +86,13 @@

{{ "paymentMethod" | i18n }}

- - + + + + (); protected initialPaymentMethod: PaymentMethodType; - protected taxInformation!: TaxInformation; protected readonly ResultType = TRIAL_PAYMENT_METHOD_DIALOG_RESULT_TYPE; pricingSummaryData!: PricingSummaryData; + formGroup = new FormGroup({ + paymentMethod: EnterPaymentMethodComponent.getFormGroup(), + billingAddress: EnterBillingAddressComponent.getFormGroup(), + }); + + private destroy$ = new Subject(); + constructor( @Inject(DIALOG_DATA) private dialogParams: TrialPaymentDialogParams, private dialogRef: DialogRef, @@ -93,8 +110,9 @@ export class TrialPaymentDialogComponent implements OnInit { private pricingSummaryService: PricingSummaryService, private apiService: ApiService, private toastService: ToastService, - private billingApiService: BillingApiServiceAbstraction, private organizationBillingApiServiceAbstraction: OrganizationBillingApiServiceAbstraction, + private subscriberBillingClient: SubscriberBillingClient, + private taxClient: TaxClient, ) { this.initialPaymentMethod = this.dialogParams.initialPaymentMethod ?? PaymentMethodType.Card; } @@ -134,19 +152,48 @@ export class TrialPaymentDialogComponent implements OnInit { : PlanInterval.Monthly; } - const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId); - this.taxInformation = TaxInformation.from(taxInfo); + const billingAddress = await this.subscriberBillingClient.getBillingAddress({ + type: "organization", + data: this.organization, + }); - this.pricingSummaryData = await this.pricingSummaryService.getPricingSummaryData( - this.currentPlan, - this.sub, - this.organization, - this.selectedInterval, - this.taxInformation, - this.isSecretsManagerTrial(), - ); + if (billingAddress) { + const { taxId, ...location } = billingAddress; + + this.formGroup.controls.billingAddress.patchValue({ + ...location, + taxId: taxId ? taxId.value : null, + }); + } + + await this.refreshPricingSummary(); this.plans = await this.apiService.getPlans(); + + combineLatest([ + this.formGroup.controls.billingAddress.controls.country.valueChanges.pipe( + startWith(this.formGroup.controls.billingAddress.controls.country.value), + ), + this.formGroup.controls.billingAddress.controls.postalCode.valueChanges.pipe( + startWith(this.formGroup.controls.billingAddress.controls.postalCode.value), + ), + this.formGroup.controls.billingAddress.controls.taxId.valueChanges.pipe( + startWith(this.formGroup.controls.billingAddress.controls.taxId.value), + ), + ]) + .pipe( + debounceTime(500), + switchMap(() => { + return this.refreshPricingSummary(); + }), + takeUntil(this.destroy$), + ) + .subscribe(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); } static open = ( @@ -175,14 +222,7 @@ export class TrialPaymentDialogComponent implements OnInit { await this.selectPlan(); - this.pricingSummaryData = await this.pricingSummaryService.getPricingSummaryData( - this.currentPlan, - this.sub, - this.organization, - this.selectedInterval, - this.taxInformation, - this.isSecretsManagerTrial(), - ); + await this.refreshPricingSummary(); } protected async selectPlan() { @@ -202,7 +242,7 @@ export class TrialPaymentDialogComponent implements OnInit { this.currentPlan = filteredPlans[0]; } try { - await this.refreshSalesTax(); + await this.refreshPricingSummary(); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const translatedMessage = this.i18nService.t(errorMessage); @@ -214,72 +254,57 @@ export class TrialPaymentDialogComponent implements OnInit { } } - protected get showTaxIdField(): boolean { - switch (this.currentPlan.productTier) { - case ProductTierType.Free: - case ProductTierType.Families: - return false; - default: - return true; - } - } - - private async refreshSalesTax(): Promise { - if ( - this.taxInformation === undefined || - !this.taxInformation.country || - !this.taxInformation.postalCode - ) { - return; - } - - const request: PreviewOrganizationInvoiceRequest = { - organizationId: this.organizationId, - passwordManager: { - additionalStorage: 0, - plan: this.currentPlan?.type, - seats: this.sub.seats, - }, - taxInformation: { - postalCode: this.taxInformation.postalCode, - country: this.taxInformation.country, - taxId: this.taxInformation.taxId, - }, - }; - - if (this.organization.useSecretsManager) { - request.secretsManager = { - seats: this.sub.smSeats ?? 0, - additionalMachineAccounts: - (this.sub.smServiceAccounts ?? 0) - - (this.sub.plan.SecretsManager?.baseServiceAccount ?? 0), - }; - } - + private refreshPricingSummary = async () => { + const estimatedTax = await this.getEstimatedTax(); this.pricingSummaryData = await this.pricingSummaryService.getPricingSummaryData( this.currentPlan, this.sub, this.organization, this.selectedInterval, - this.taxInformation, this.isSecretsManagerTrial(), + estimatedTax, ); - } + }; - async taxInformationChanged(event: TaxInformation) { - this.taxInformation = event; - this.toggleBankAccount(); - await this.refreshSalesTax(); - } + private getEstimatedTax = async () => { + if (this.formGroup.controls.billingAddress.invalid) { + return 0; + } - toggleBankAccount = () => { - this.paymentComponent.showBankAccount = this.taxInformation.country === "US"; + const cadence = + this.currentPlan.productTier !== ProductTierType.Families + ? this.currentPlan.isAnnual + ? "annually" + : "monthly" + : null; - if ( - !this.paymentComponent.showBankAccount && - this.paymentComponent.selected === PaymentMethodType.BankAccount - ) { - this.paymentComponent.select(PaymentMethodType.Card); + const billingAddress = getBillingAddressFromForm(this.formGroup.controls.billingAddress); + + const getTierFromLegacyEnum = (organization: Organization) => { + switch (organization.productTierType) { + case ProductTierType.Families: + return "families"; + case ProductTierType.Teams: + return "teams"; + case ProductTierType.Enterprise: + return "enterprise"; + } + }; + + const tier = getTierFromLegacyEnum(this.organization); + + if (tier && cadence) { + const costs = await this.taxClient.previewTaxForOrganizationSubscriptionPlanChange( + this.organization.id, + { + tier, + cadence, + }, + billingAddress, + ); + return costs.tax; + } else { + return 0; } }; @@ -292,15 +317,24 @@ export class TrialPaymentDialogComponent implements OnInit { } async onSubscribe(): Promise { - if (!this.taxComponent.validate()) { - this.taxComponent.markAllAsTouched(); + this.formGroup.markAllAsTouched(); + if (this.formGroup.invalid) { + return; } + try { - await this.updateOrganizationPaymentMethod( - this.organizationId, - this.paymentComponent, - this.taxInformation, - ); + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + if (!paymentMethod) { + return; + } + + const billingAddress = getBillingAddressFromForm(this.formGroup.controls.billingAddress); + + const subscriber: BitwardenSubscriber = { type: "organization", data: this.organization }; + await Promise.all([ + this.subscriberBillingClient.updatePaymentMethod(subscriber, paymentMethod, null), + this.subscriberBillingClient.updateBillingAddress(subscriber, billingAddress), + ]); if (this.currentPlan.type !== this.sub.planType) { const changePlanRequest = new ChangePlanFrequencyRequest(); @@ -332,20 +366,6 @@ export class TrialPaymentDialogComponent implements OnInit { } } - private async updateOrganizationPaymentMethod( - organizationId: string, - paymentComponent: PaymentComponent, - taxInformation: TaxInformation, - ): Promise { - const paymentSource = await paymentComponent.tokenize(); - - const request = new UpdatePaymentMethodRequest(); - request.paymentSource = paymentSource; - request.taxInformation = ExpandedTaxInfoUpdateRequest.From(taxInformation); - - await this.billingApiService.updateOrganizationPaymentMethod(organizationId, request); - } - resolvePlanName(productTier: ProductTierType): string { switch (productTier) { case ProductTierType.Enterprise: @@ -362,4 +382,11 @@ export class TrialPaymentDialogComponent implements OnInit { return this.i18nService.t("planNameFree"); } } + + get supportsTaxId() { + if (!this.organization) { + return false; + } + return this.organization.productTierType !== ProductTierType.Families; + } } diff --git a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.html b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.html deleted file mode 100644 index 1367e6e3082..00000000000 --- a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.html +++ /dev/null @@ -1,12 +0,0 @@ - -

{{ "verifyBankAccountWithStatementDescriptorInstructions" | i18n }}

-
- - {{ "descriptorCode" | i18n }} - - - -
-
diff --git a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts deleted file mode 100644 index b7cdfbe60a2..00000000000 --- a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { FormBuilder, FormControl, Validators } from "@angular/forms"; - -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; - -import { SharedModule } from "../../../shared"; - -@Component({ - selector: "app-verify-bank-account", - templateUrl: "./verify-bank-account.component.html", - imports: [SharedModule], -}) -export class VerifyBankAccountComponent { - @Input() onSubmit?: (request: VerifyBankAccountRequest) => Promise; - @Output() submitted = new EventEmitter(); - - protected formGroup = this.formBuilder.group({ - descriptorCode: new FormControl(null, [ - Validators.required, - Validators.minLength(6), - Validators.maxLength(6), - ]), - }); - - constructor(private formBuilder: FormBuilder) {} - - submit = async () => { - const request = new VerifyBankAccountRequest(this.formGroup.value.descriptorCode); - await this.onSubmit?.(request); - this.submitted.emit(); - }; -} diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html index c1a33a4c8df..7377fc45484 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html @@ -54,17 +54,7 @@ > diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 0b1ddda0c12..baccabdc763 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -30,13 +30,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ToastService } from "@bitwarden/components"; import { UserId } from "@bitwarden/user-core"; +import { Trial } from "@bitwarden/web-vault/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service"; -import { - OrganizationCreatedEvent, - SubscriptionProduct, - TrialOrganizationType, -} from "../../../billing/accounts/trial-initiation/trial-billing-step.component"; import { RouterService } from "../../../core/router.service"; +import { OrganizationCreatedEvent } from "../trial-billing-step/trial-billing-step.component"; import { VerticalStepperComponent } from "../vertical-stepper/vertical-stepper.component"; export type InitiationPath = @@ -95,7 +92,6 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { }); private destroy$ = new Subject(); - protected readonly SubscriptionProduct = SubscriptionProduct; protected readonly ProductType = ProductType; protected trialPaymentOptional$ = this.configService.getFeatureFlag$( FeatureFlag.TrialPaymentOptional, @@ -338,14 +334,6 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { } } - get trialOrganizationType(): TrialOrganizationType | null { - if (this.productTier === ProductTierType.Free) { - return null; - } - - return this.productTier; - } - readonly showBillingStep$ = this.trialPaymentOptional$.pipe( map((trialPaymentOptional) => { return ( @@ -434,4 +422,26 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { return null; }); } + + get trial(): Trial { + const product = + this.product === ProductType.PasswordManager ? "passwordManager" : "secretsManager"; + + const tier = + this.productTier === ProductTierType.Families + ? "families" + : this.productTier === ProductTierType.Teams + ? "teams" + : "enterprise"; + + return { + organization: { + name: this.orgInfoFormGroup.value.name!, + email: this.orgInfoFormGroup.value.billingEmail!, + }, + product, + tier, + length: this.trialLength, + }; + } } diff --git a/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.html b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.html new file mode 100644 index 00000000000..51b7f0c7117 --- /dev/null +++ b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.html @@ -0,0 +1,87 @@ +@if (!(prices$ | async)) { + +} @else { + @let prices = prices$ | async; +
+
+ +
+

{{ "billingPlanLabel" | i18n }}

+ +
+ + + {{ "annual" | i18n }} - + {{ prices.annually | currency: "$" }} + /{{ "yr" | i18n }} + + +
+ @if (prices.monthly) { +
+ + + {{ "monthly" | i18n }} - + {{ prices.monthly | currency: "$" }} + /{{ "monthAbbr" | i18n }} + + +
+ } +
+
+ +
+

{{ "paymentType" | i18n }}

+ + + + @if (trial().length === 0) { + @let label = + trial().product === "passwordManager" + ? "passwordManagerPlanPrice" + : "secretsManagerPlanPrice"; +
+ @let selectionTaxAmounts = selectionCosts$ | async; +
+ {{ label | i18n }}: {{ selectionPrice$ | async | currency: "USD $" }} +
+ {{ "estimatedTax" | i18n }}: + {{ selectionTaxAmounts.tax | currency: "USD $" }} +
+
+
+

+ {{ "total" | i18n }}: + @let interval = formGroup.value.cadence === "annually" ? "year" : "month"; + {{ selectionTaxAmounts.total | currency: "USD $" }}/{{ interval | i18n }} +

+
+ } +
+ +
+ + +
+
+
+} + + + + {{ "loading" | i18n }} + diff --git a/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.ts b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.ts new file mode 100644 index 00000000000..0f185564c2e --- /dev/null +++ b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.ts @@ -0,0 +1,160 @@ +import { Component, input, OnDestroy, OnInit, output, ViewChild } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; +import { + combineLatest, + debounceTime, + filter, + map, + Observable, + shareReplay, + startWith, + switchMap, + Subject, + firstValueFrom, +} from "rxjs"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ToastService } from "@bitwarden/components"; +import { TaxClient } from "@bitwarden/web-vault/app/billing/clients"; +import { + BillingAddressControls, + EnterBillingAddressComponent, + EnterPaymentMethodComponent, +} from "@bitwarden/web-vault/app/billing/payment/components"; +import { + Cadence, + Cadences, + Prices, + Trial, + TrialBillingStepService, +} from "@bitwarden/web-vault/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +export interface OrganizationCreatedEvent { + organizationId: string; + planDescription: string; +} + +@Component({ + selector: "app-trial-billing-step", + templateUrl: "./trial-billing-step.component.html", + imports: [EnterPaymentMethodComponent, EnterBillingAddressComponent, SharedModule], + providers: [TaxClient, TrialBillingStepService], +}) +export class TrialBillingStepComponent implements OnInit, OnDestroy { + @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent!: EnterPaymentMethodComponent; + + protected trial = input.required(); + protected steppedBack = output(); + protected organizationCreated = output(); + + private destroy$ = new Subject(); + + protected prices$!: Observable; + + protected selectionPrice$!: Observable; + protected selectionCosts$!: Observable<{ + tax: number; + total: number; + }>; + protected selectionDescription$!: Observable; + + protected formGroup = new FormGroup({ + cadence: new FormControl(Cadences.Annually, { + nonNullable: true, + }), + paymentMethod: EnterPaymentMethodComponent.getFormGroup(), + billingAddress: EnterBillingAddressComponent.getFormGroup(), + }); + + constructor( + private i18nService: I18nService, + private toastService: ToastService, + private trialBillingStepService: TrialBillingStepService, + ) {} + + async ngOnInit() { + const { product, tier } = this.trial(); + this.prices$ = this.trialBillingStepService.getPrices$(product, tier); + + const cadenceChanged = this.formGroup.controls.cadence.valueChanges.pipe( + startWith(Cadences.Annually), + ); + + this.selectionPrice$ = combineLatest([this.prices$, cadenceChanged]).pipe( + map(([prices, cadence]) => prices[cadence]), + filter((price): price is number => !!price), + ); + + this.selectionCosts$ = combineLatest([ + cadenceChanged, + this.formGroup.controls.billingAddress.valueChanges.pipe( + startWith(this.formGroup.controls.billingAddress.value), + filter( + (billingAddress): billingAddress is BillingAddressControls => + !!billingAddress.country && !!billingAddress.postalCode, + ), + ), + ]).pipe( + debounceTime(500), + switchMap(([cadence, billingAddress]) => + this.trialBillingStepService.getCosts(product, tier, cadence, billingAddress), + ), + startWith({ + tax: 0, + total: 0, + }), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + this.selectionDescription$ = combineLatest([this.selectionPrice$, cadenceChanged]).pipe( + map(([price, cadence]) => { + switch (cadence) { + case Cadences.Annually: + return `${this.i18nService.t("annual")} ($${price}/${this.i18nService.t("yr")})`; + case Cadences.Monthly: + return `${this.i18nService.t("monthly")} ($${price}/${this.i18nService.t("monthAbbr")})`; + } + }), + ); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + submit = async (): Promise => { + this.formGroup.markAllAsTouched(); + if (this.formGroup.invalid) { + return; + } + + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + if (!paymentMethod) { + return; + } + + const billingAddress = this.formGroup.controls.billingAddress.getRawValue(); + + const organization = await this.trialBillingStepService.startTrial( + this.trial(), + this.formGroup.value.cadence!, + billingAddress, + paymentMethod, + ); + + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("organizationCreated"), + message: this.i18nService.t("organizationReadyToGo"), + }); + + this.organizationCreated.emit({ + organizationId: organization.id, + planDescription: await firstValueFrom(this.selectionDescription$), + }); + }; + + protected stepBack = () => this.steppedBack.emit(); +} diff --git a/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service.ts b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service.ts new file mode 100644 index 00000000000..9e4f45ede92 --- /dev/null +++ b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service.ts @@ -0,0 +1,209 @@ +import { Injectable } from "@angular/core"; +import { firstValueFrom, from, map, shareReplay } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { + OrganizationBillingServiceAbstraction, + SubscriptionInformation, +} from "@bitwarden/common/billing/abstractions"; +import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; +import { TaxClient } from "@bitwarden/web-vault/app/billing/clients"; +import { + BillingAddressControls, + getBillingAddressFromControls, +} from "@bitwarden/web-vault/app/billing/payment/components"; +import { + tokenizablePaymentMethodToLegacyEnum, + TokenizedPaymentMethod, +} from "@bitwarden/web-vault/app/billing/payment/types"; + +export const Tiers = { + Families: "families", + Teams: "teams", + Enterprise: "enterprise", +} as const; + +export const Cadences = { + Annually: "annually", + Monthly: "monthly", +} as const; + +export const Products = { + PasswordManager: "passwordManager", + SecretsManager: "secretsManager", +} as const; + +export type Tier = (typeof Tiers)[keyof typeof Tiers]; +export type Cadence = (typeof Cadences)[keyof typeof Cadences]; +export type Product = (typeof Products)[keyof typeof Products]; + +export type Prices = { + [Cadences.Annually]: number; + [Cadences.Monthly]?: number; +}; + +export interface Trial { + organization: { + name: string; + email: string; + }; + product: Product; + tier: Tier; + length: number; +} + +@Injectable() +export class TrialBillingStepService { + constructor( + private accountService: AccountService, + private apiService: ApiService, + private organizationBillingService: OrganizationBillingServiceAbstraction, + private taxClient: TaxClient, + ) {} + + private plans$ = from(this.apiService.getPlans()).pipe( + shareReplay({ bufferSize: 1, refCount: true }), + ); + + getPrices$ = (product: Product, tier: Tier) => + this.plans$.pipe( + map((plans) => { + switch (tier) { + case "families": { + const annually = plans.data.find((plan) => plan.type === PlanType.FamiliesAnnually); + return { + annually: annually!.PasswordManager.basePrice, + }; + } + case "teams": + case "enterprise": { + const annually = plans.data.find( + (plan) => + plan.type === + (tier === "teams" ? PlanType.TeamsAnnually : PlanType.EnterpriseAnnually), + ); + const monthly = plans.data.find( + (plan) => + plan.type === + (tier === "teams" ? PlanType.TeamsMonthly : PlanType.EnterpriseMonthly), + ); + switch (product) { + case "passwordManager": { + return { + annually: annually!.PasswordManager.seatPrice, + monthly: monthly!.PasswordManager.seatPrice, + }; + } + case "secretsManager": { + return { + annually: annually!.SecretsManager.seatPrice, + monthly: monthly!.SecretsManager.seatPrice, + }; + } + } + } + } + }), + ); + + getCosts = async ( + product: Product, + tier: Tier, + cadence: Cadence, + billingAddressControls: BillingAddressControls, + ): Promise<{ + tax: number; + total: number; + }> => { + const billingAddress = getBillingAddressFromControls(billingAddressControls); + return await this.taxClient.previewTaxForOrganizationSubscriptionPurchase( + { + tier, + cadence, + passwordManager: { + seats: 1, + additionalStorage: 0, + sponsored: false, + }, + secretsManager: + product === "secretsManager" + ? { + seats: 1, + additionalServiceAccounts: 0, + standalone: true, + } + : undefined, + }, + billingAddress, + ); + }; + + startTrial = async ( + trial: Trial, + cadence: Cadence, + billingAddress: BillingAddressControls, + paymentMethod: TokenizedPaymentMethod, + ): Promise => { + const getPlanType = async (tier: Tier, cadence: Cadence) => { + const plans = await firstValueFrom(this.plans$); + switch (tier) { + case "families": + return plans.data.find((plan) => plan.type === PlanType.FamiliesAnnually)!.type; + case "teams": + return plans.data.find( + (plan) => + plan.type === + (cadence === "annually" ? PlanType.TeamsAnnually : PlanType.TeamsMonthly), + )!.type; + case "enterprise": + return plans.data.find( + (plan) => + plan.type === + (cadence === "annually" ? PlanType.EnterpriseAnnually : PlanType.EnterpriseMonthly), + )!.type; + } + }; + + const legacyPaymentMethod: [string, PaymentMethodType] = [ + paymentMethod.token, + tokenizablePaymentMethodToLegacyEnum(paymentMethod.type), + ]; + const planType = await getPlanType(trial.tier, cadence); + + const request: SubscriptionInformation = { + organization: { + name: trial.organization.name, + billingEmail: trial.organization.email, + initiationPath: + trial.product === "passwordManager" + ? "Password Manager trial from marketing website" + : "Secrets Manager trial from marketing website", + }, + plan: + trial.product === "passwordManager" + ? { type: planType, passwordManagerSeats: 1 } + : { + type: planType, + passwordManagerSeats: 1, + subscribeToSecretsManager: true, + isFromSecretsManagerTrial: true, + secretsManagerSeats: 1, + }, + payment: { + paymentMethod: legacyPaymentMethod, + billing: { + country: billingAddress.country, + postalCode: billingAddress.postalCode, + taxId: billingAddress.taxId ?? undefined, + }, + skipTrial: trial.length === 0, + }, + }; + + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + return await this.organizationBillingService.purchaseSubscription(request, activeUserId); + }; +} diff --git a/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts b/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts index 06e1cce7f23..cd393f0dd5e 100644 --- a/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts +++ b/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts @@ -6,11 +6,11 @@ import { InputPasswordComponent } from "@bitwarden/auth/angular"; import { FormFieldModule } from "@bitwarden/components"; import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module"; -import { TrialBillingStepComponent } from "../../billing/accounts/trial-initiation/trial-billing-step.component"; import { SharedModule } from "../../shared"; import { CompleteTrialInitiationComponent } from "./complete-trial-initiation/complete-trial-initiation.component"; import { ConfirmationDetailsComponent } from "./confirmation-details.component"; +import { TrialBillingStepComponent } from "./trial-billing-step/trial-billing-step.component"; import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.module"; @NgModule({ diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index 011e7a3bce6..cd32eaf2858 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -5,8 +5,6 @@ import { filter, firstValueFrom, map, Observable, switchMap } from "rxjs"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { MessageListener } from "@bitwarden/common/platform/messaging"; import { UserId } from "@bitwarden/common/types/guid"; import { BannerModule } from "@bitwarden/components"; @@ -41,7 +39,6 @@ export class VaultBannersComponent implements OnInit { private router: Router, private accountService: AccountService, private messageListener: MessageListener, - private configService: ConfigService, ) { this.premiumBannerVisible$ = this.activeUserId$.pipe( filter((userId): userId is UserId => userId != null), @@ -75,16 +72,12 @@ export class VaultBannersComponent implements OnInit { } async navigateToPaymentMethod(organizationId: string): Promise { - const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, - ); - const route = managePaymentDetailsOutsideCheckout ? "payment-details" : "payment-method"; const navigationExtras = { state: { launchPaymentModalAutomatically: true }, }; await this.router.navigate( - ["organizations", organizationId, "billing", route], + ["organizations", organizationId, "billing", "payment-details"], navigationExtras, ); } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html index 31e56836375..724e5891dc8 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html @@ -39,12 +39,7 @@ *ngIf="canAccessBilling$ | async" > - @if (managePaymentDetailsOutsideCheckout$ | async) { - - } + ; protected clientsTranslationKey$: Observable; - protected managePaymentDetailsOutsideCheckout$: Observable; protected providerPortalTakeover$: Observable; protected subscriber$: Observable; @@ -100,10 +99,6 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy { ), ); - this.managePaymentDetailsOutsideCheckout$ = this.configService.getFeatureFlag$( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, - ); - this.provider$ .pipe( switchMap((provider) => diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 263b90f5b32..24e8a757bdf 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -7,14 +7,18 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CardComponent, ScrollLayoutDirective, SearchModule } from "@bitwarden/components"; import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component"; import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; -import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/payment/payment.component"; -import { VerifyBankAccountComponent } from "@bitwarden/web-vault/app/billing/shared/verify-bank-account/verify-bank-account.component"; +import { + EnterBillingAddressComponent, + EnterPaymentMethodComponent, +} from "@bitwarden/web-vault/app/billing/payment/components"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { CreateClientDialogComponent, + InvoicesComponent, ManageClientNameDialogComponent, ManageClientSubscriptionDialogComponent, + NoInvoicesComponent, ProviderBillingHistoryComponent, ProviderSubscriptionComponent, ProviderSubscriptionStatusComponent, @@ -52,11 +56,11 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr ProvidersLayoutComponent, DangerZoneComponent, ScrollingModule, - VerifyBankAccountComponent, CardComponent, ScrollLayoutDirective, - PaymentComponent, ProviderWarningsModule, + EnterPaymentMethodComponent, + EnterBillingAddressComponent, ], declarations: [ AcceptProviderComponent, @@ -72,8 +76,10 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr AddEditMemberDialogComponent, AddExistingOrganizationDialogComponent, CreateClientDialogComponent, + InvoicesComponent, ManageClientNameDialogComponent, ManageClientSubscriptionDialogComponent, + NoInvoicesComponent, ProviderBillingHistoryComponent, ProviderSubscriptionComponent, ProviderSubscriptionStatusComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index daae7e2ed2e..43f49b1fd04 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -29,13 +29,11 @@

{{ "paymentMethod" | i18n }}

- - + + diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index 72ca0bc8391..0fa69c7a0e6 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -1,25 +1,24 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, Subject, switchMap } from "rxjs"; import { first, takeUntil } from "rxjs/operators"; -import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ProviderKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; -import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/payment/payment.component"; +import { + EnterBillingAddressComponent, + EnterPaymentMethodComponent, + getBillingAddressFromForm, +} from "@bitwarden/web-vault/app/billing/payment/components"; @Component({ selector: "provider-setup", @@ -27,16 +26,17 @@ import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/paymen standalone: false, }) export class SetupComponent implements OnInit, OnDestroy { - @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; - @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent; + @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent!: EnterPaymentMethodComponent; loading = true; - providerId: string; - token: string; + providerId!: string; + token!: string; protected formGroup = this.formBuilder.group({ name: ["", Validators.required], billingEmail: ["", [Validators.required, Validators.email]], + paymentMethod: EnterPaymentMethodComponent.getFormGroup(), + billingAddress: EnterBillingAddressComponent.getFormGroup(), }); private destroy$ = new Subject(); @@ -69,7 +69,7 @@ export class SetupComponent implements OnInit, OnDestroy { if (error) { this.toastService.showToast({ variant: "error", - title: null, + title: "", message: this.i18nService.t("emergencyInviteAcceptFailed"), timeout: 10000, }); @@ -95,6 +95,7 @@ export class SetupComponent implements OnInit, OnDestroy { replaceUrl: true, }); } + this.loading = false; } catch (error) { this.validationService.showError(error); @@ -115,10 +116,7 @@ export class SetupComponent implements OnInit, OnDestroy { try { this.formGroup.markAllAsTouched(); - const paymentValid = this.paymentComponent.validate(); - const taxInformationValid = this.taxInformationComponent.validate(); - - if (!paymentValid || !taxInformationValid || !this.formGroup.valid) { + if (this.formGroup.invalid) { return; } const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); @@ -126,29 +124,24 @@ export class SetupComponent implements OnInit, OnDestroy { const key = providerKey[0].encryptedString; const request = new ProviderSetupRequest(); - request.name = this.formGroup.value.name; - request.billingEmail = this.formGroup.value.billingEmail; + request.name = this.formGroup.value.name!; + request.billingEmail = this.formGroup.value.billingEmail!; request.token = this.token; - request.key = key; + request.key = key!; - request.taxInfo = new ExpandedTaxInfoUpdateRequest(); - const taxInformation = this.taxInformationComponent.getTaxInformation(); + const paymentMethod = await this.enterPaymentMethodComponent.tokenize(); + if (!paymentMethod) { + return; + } - request.taxInfo.country = taxInformation.country; - request.taxInfo.postalCode = taxInformation.postalCode; - request.taxInfo.taxId = taxInformation.taxId; - request.taxInfo.line1 = taxInformation.line1; - request.taxInfo.line2 = taxInformation.line2; - request.taxInfo.city = taxInformation.city; - request.taxInfo.state = taxInformation.state; - - request.paymentSource = await this.paymentComponent.tokenize(); + request.paymentMethod = paymentMethod; + request.billingAddress = getBillingAddressFromForm(this.formGroup.controls.billingAddress); const provider = await this.providerApiService.postProviderSetup(this.providerId, request); this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("providerSetup"), }); @@ -156,20 +149,10 @@ export class SetupComponent implements OnInit, OnDestroy { await this.router.navigate(["/providers", provider.id]); } catch (e) { - if ( - this.paymentComponent.selected === PaymentMethodType.PayPal && - typeof e === "string" && - e === "No payment method is available." - ) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("clickPayWithPayPal"), - }); - } else { + if (e !== null && typeof e === "object" && "message" in e && typeof e.message === "string") { e.message = this.i18nService.translate(e.message) || e.message; - this.validationService.showError(e); } + this.validationService.showError(e); } }; } diff --git a/libs/angular/src/billing/components/invoices/invoices.component.html b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/invoices.component.html similarity index 100% rename from libs/angular/src/billing/components/invoices/invoices.component.html rename to bitwarden_license/bit-web/src/app/billing/providers/billing-history/invoices.component.html diff --git a/libs/angular/src/billing/components/invoices/invoices.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/invoices.component.ts similarity index 100% rename from libs/angular/src/billing/components/invoices/invoices.component.ts rename to bitwarden_license/bit-web/src/app/billing/providers/billing-history/invoices.component.ts diff --git a/libs/angular/src/billing/components/invoices/no-invoices.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/no-invoices.component.ts similarity index 100% rename from libs/angular/src/billing/components/invoices/no-invoices.component.ts rename to bitwarden_license/bit-web/src/app/billing/providers/billing-history/no-invoices.component.ts diff --git a/bitwarden_license/bit-web/src/app/billing/providers/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/index.ts index b1294bc8047..3cd83e68990 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/index.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/index.ts @@ -1,3 +1,5 @@ +export * from "./billing-history/invoices.component"; +export * from "./billing-history/no-invoices.component"; export * from "./billing-history/provider-billing-history.component"; export * from "./clients"; export * from "./guards/has-consolidated-billing.guard"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.ts index d2ac2cede2f..5a070687de4 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/payment-details/provider-payment-details.component.ts @@ -1,13 +1,10 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { BehaviorSubject, combineLatest, - EMPTY, filter, firstValueFrom, - from, - map, merge, Observable, of, @@ -19,7 +16,6 @@ import { tap, withLatestFrom, } from "rxjs"; -import { catchError } from "rxjs/operators"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; @@ -49,13 +45,6 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { ProviderWarningsService } from "../warnings/services"; -class RedirectError { - constructor( - public path: string[], - public relativeTo: ActivatedRoute, - ) {} -} - type View = { activeUserId: UserId; provider: BitwardenSubscriber; @@ -92,18 +81,6 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy { ); private load$: Observable = this.provider$.pipe( - switchMap((provider) => - this.configService - .getFeatureFlag$(FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout) - .pipe( - map((managePaymentDetailsOutsideCheckout) => { - if (!managePaymentDetailsOutsideCheckout) { - throw new RedirectError(["../subscription"], this.activatedRoute); - } - return provider; - }), - ), - ), mapProviderToSubscriber, switchMap(async (provider) => { const getTaxIdWarning = firstValueFrom( @@ -131,14 +108,6 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy { }; }), shareReplay({ bufferSize: 1, refCount: false }), - catchError((error: unknown) => { - if (error instanceof RedirectError) { - return from(this.router.navigate(error.path, { relativeTo: error.relativeTo })).pipe( - switchMap(() => EMPTY), - ); - } - throw error; - }), ); view$: Observable = merge( @@ -158,7 +127,6 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy { private messageListener: MessageListener, private providerService: ProviderService, private providerWarningsService: ProviderWarningsService, - private router: Router, private subscriberBillingClient: SubscriberBillingClient, ) {} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html index 0205d2838d1..05eda7e7ea4 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html @@ -62,51 +62,5 @@
- @if (!managePaymentDetailsOutsideCheckout) { - - -

- {{ "accountCredit" | i18n }} -

-

{{ subscription.accountCredit | currency: "$" }}

-

{{ "creditAppliedDesc" | i18n }}

-
- - -

{{ "paymentMethod" | i18n }}

-

- {{ "noPaymentMethod" | i18n }} -

- - - -

- - {{ subscription.paymentSource.description }} - - {{ "unverified" | i18n }} -

-
- -
- - -

{{ "taxInformation" | i18n }}

-

{{ "taxInformationDesc" | i18n }}

- -
- } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index 83a23760d80..98aceb0f878 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -2,26 +2,14 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { concatMap, lastValueFrom, Subject, takeUntil } from "rxjs"; +import { concatMap, Subject, takeUntil } from "rxjs"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { TaxInformation } from "@bitwarden/common/billing/models/domain"; -import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; -import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; import { ProviderPlanResponse, ProviderSubscriptionResponse, } from "@bitwarden/common/billing/models/response/provider-subscription-response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService, ToastService } from "@bitwarden/components"; import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service"; -import { - AdjustPaymentDialogComponent, - AdjustPaymentDialogResultType, -} from "@bitwarden/web-vault/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component"; @Component({ selector: "app-provider-subscription", @@ -36,18 +24,11 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { protected loading: boolean; private destroy$ = new Subject(); protected totalCost: number; - protected managePaymentDetailsOutsideCheckout: boolean; - - protected readonly TaxInformation = TaxInformation; constructor( private billingApiService: BillingApiServiceAbstraction, - private i18nService: I18nService, private route: ActivatedRoute, private billingNotificationService: BillingNotificationService, - private dialogService: DialogService, - private toastService: ToastService, - private configService: ConfigService, ) {} async ngOnInit() { @@ -55,9 +36,6 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { .pipe( concatMap(async (params) => { this.providerId = params.providerId; - this.managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, - ); await this.load(); this.firstLoaded = true; }), @@ -83,40 +61,6 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { } } - protected updatePaymentMethod = async (): Promise => { - const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { - data: { - initialPaymentMethod: this.subscription.paymentSource?.type, - providerId: this.providerId, - }, - }); - - const result = await lastValueFrom(dialogRef.closed); - - if (result === AdjustPaymentDialogResultType.Submitted) { - await this.load(); - } - }; - - protected updateTaxInformation = async (taxInformation: TaxInformation) => { - try { - const request = ExpandedTaxInfoUpdateRequest.From(taxInformation); - await this.billingApiService.updateProviderTaxInformation(this.providerId, request); - this.billingNotificationService.showSuccess(this.i18nService.t("updatedTaxInformation")); - } catch (error) { - this.billingNotificationService.handleError(error); - } - }; - - protected verifyBankAccount = async (request: VerifyBankAccountRequest): Promise => { - await this.billingApiService.verifyProviderBankAccount(this.providerId, request); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("verifiedBankAccount"), - }); - }; - protected getFormattedCost( cost: number, seatMinimum: number, @@ -161,7 +105,7 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { } protected getBillingCadenceLabel(providerPlanResponse: ProviderPlanResponse): string { - if (providerPlanResponse == null || providerPlanResponse == undefined) { + if (providerPlanResponse == null) { return "month"; } @@ -174,27 +118,4 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { return "month"; } } - - protected get paymentSourceClasses() { - if (this.subscription.paymentSource == null) { - return []; - } - switch (this.subscription.paymentSource.type) { - case PaymentMethodType.Card: - return ["bwi-credit-card"]; - case PaymentMethodType.BankAccount: - case PaymentMethodType.Check: - return ["bwi-billing"]; - case PaymentMethodType.PayPal: - return ["bwi-paypal text-primary"]; - default: - return []; - } - } - - protected get updatePaymentSourceButtonText(): string { - const key = - this.subscription.paymentSource == null ? "addPaymentMethod" : "changePaymentMethod"; - return this.i18nService.t(key); - } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/warnings/types/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/warnings/types/index.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index d9ff3ec5619..e301c0462c3 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -23,8 +23,6 @@ import { import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -117,7 +115,6 @@ export class OverviewComponent implements OnInit, OnDestroy { private smOnboardingTasksService: SMOnboardingTasksService, private logService: LogService, private router: Router, - private configService: ConfigService, ) {} ngOnInit() { @@ -218,13 +215,12 @@ export class OverviewComponent implements OnInit, OnDestroy { } async navigateToPaymentMethod() { - const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag( - FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout, + await this.router.navigate( + ["organizations", `${this.organizationId}`, "billing", "payment-details"], + { + state: { launchPaymentModalAutomatically: true }, + }, ); - const route = managePaymentDetailsOutsideCheckout ? "payment-details" : "payment-method"; - await this.router.navigate(["organizations", `${this.organizationId}`, "billing", route], { - state: { launchPaymentModalAutomatically: true }, - }); } ngOnDestroy(): void { diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.html b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.html deleted file mode 100644 index c9c0c296ada..00000000000 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.html +++ /dev/null @@ -1,55 +0,0 @@ -
- - -

{{ "creditDelayed" | i18n }}

-
- - - {{ "payPal" | i18n }} - - - {{ "bitcoin" | i18n }} - - -
-
- - {{ "amount" | i18n }} - - $USD - -
-
- - - - -
-
-
- - - - - - - - - - - - - - - -
diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts deleted file mode 100644 index 3dc56c55b0c..00000000000 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ /dev/null @@ -1,167 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, ElementRef, Inject, OnInit, ViewChild } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { firstValueFrom, map } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { AccountService, AccountInfo } from "@bitwarden/common/auth/abstractions/account.service"; -import { PaymentMethodType } from "@bitwarden/common/billing/enums"; -import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components"; - -export type AddAccountCreditDialogParams = { - organizationId?: string; - providerId?: string; -}; - -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum AddAccountCreditDialogResultType { - Closed = "closed", - Submitted = "submitted", -} - -export const openAddAccountCreditDialog = ( - dialogService: DialogService, - dialogConfig: DialogConfig, -) => - dialogService.open( - AddAccountCreditDialogComponent, - dialogConfig, - ); - -type PayPalConfig = { - businessId?: string; - buttonAction?: string; - returnUrl?: string; - customField?: string; - subject?: string; -}; - -@Component({ - templateUrl: "./add-account-credit-dialog.component.html", - standalone: false, -}) -export class AddAccountCreditDialogComponent implements OnInit { - @ViewChild("payPalForm", { read: ElementRef, static: true }) payPalForm: ElementRef; - protected formGroup = new FormGroup({ - paymentMethod: new FormControl(PaymentMethodType.PayPal), - creditAmount: new FormControl(null, [Validators.required, Validators.min(0.01)]), - }); - protected payPalConfig: PayPalConfig; - protected ResultType = AddAccountCreditDialogResultType; - - private organization?: Organization; - private provider?: Provider; - private user?: { id: UserId } & AccountInfo; - - constructor( - private accountService: AccountService, - private apiService: ApiService, - private configService: ConfigService, - @Inject(DIALOG_DATA) private dialogParams: AddAccountCreditDialogParams, - private dialogRef: DialogRef, - private organizationService: OrganizationService, - private platformUtilsService: PlatformUtilsService, - private providerService: ProviderService, - ) { - this.payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig; - } - - protected readonly paymentMethodType = PaymentMethodType; - - submit = async () => { - this.formGroup.markAllAsTouched(); - - if (this.formGroup.invalid) { - return; - } - - if (this.formGroup.value.paymentMethod === PaymentMethodType.PayPal) { - this.payPalForm.nativeElement.submit(); - return; - } - - if (this.formGroup.value.paymentMethod === PaymentMethodType.BitPay) { - const request = this.getBitPayInvoiceRequest(); - const bitPayUrl = await this.apiService.postBitPayInvoice(request); - this.platformUtilsService.launchUri(bitPayUrl); - return; - } - - this.dialogRef.close(AddAccountCreditDialogResultType.Submitted); - }; - - async ngOnInit(): Promise { - let payPalCustomField: string; - - if (this.dialogParams.organizationId) { - this.formGroup.patchValue({ - creditAmount: 20.0, - }); - this.user = await firstValueFrom(this.accountService.activeAccount$); - this.organization = await firstValueFrom( - this.organizationService - .organizations$(this.user.id) - .pipe( - map((organizations) => - organizations.find((org) => org.id === this.dialogParams.organizationId), - ), - ), - ); - payPalCustomField = "organization_id:" + this.organization.id; - this.payPalConfig.subject = this.organization.name; - } else if (this.dialogParams.providerId) { - this.formGroup.patchValue({ - creditAmount: 20.0, - }); - this.provider = await firstValueFrom( - this.providerService.get$(this.dialogParams.providerId, this.user.id), - ); - payPalCustomField = "provider_id:" + this.provider.id; - this.payPalConfig.subject = this.provider.name; - } else { - this.formGroup.patchValue({ - creditAmount: 10.0, - }); - payPalCustomField = "user_id:" + this.user.id; - this.payPalConfig.subject = this.user.email; - } - - const region = await firstValueFrom(this.configService.cloudRegion$); - - payPalCustomField += ",account_credit:1"; - payPalCustomField += `,region:${region}`; - - this.payPalConfig.customField = payPalCustomField; - this.payPalConfig.returnUrl = window.location.href; - } - - getBitPayInvoiceRequest(): BitPayInvoiceRequest { - const request = new BitPayInvoiceRequest(); - if (this.organization) { - request.name = this.organization.name; - request.organizationId = this.organization.id; - } else if (this.provider) { - request.name = this.provider.name; - request.providerId = this.provider.id; - } else { - request.email = this.user.email; - request.userId = this.user.id; - } - - request.credit = true; - request.amount = this.formGroup.value.creditAmount; - request.returnUrl = window.location.href; - - return request; - } -} diff --git a/libs/angular/src/billing/components/index.ts b/libs/angular/src/billing/components/index.ts index dacb5b265bd..34e1d27c1ed 100644 --- a/libs/angular/src/billing/components/index.ts +++ b/libs/angular/src/billing/components/index.ts @@ -1,5 +1 @@ -export * from "./add-account-credit-dialog/add-account-credit-dialog.component"; -export * from "./invoices/invoices.component"; -export * from "./invoices/no-invoices.component"; -export * from "./manage-tax-information/manage-tax-information.component"; export * from "./premium.component"; diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html deleted file mode 100644 index 391765251b0..00000000000 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html +++ /dev/null @@ -1,90 +0,0 @@ -
-
-
- - {{ "country" | i18n }} - - - - -
-
- - {{ "zipPostalCode" | i18n }} - - -
- -
- - {{ "address1" | i18n }} - - -
-
- - {{ "address2" | i18n }} - - -
-
- - {{ "cityTown" | i18n }} - - -
-
- - {{ "stateProvince" | i18n }} - - -
-
- - {{ "taxIdNumber" | i18n }} - - -
-
-
- -
-
-
diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.spec.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.spec.ts deleted file mode 100644 index c662e20b275..00000000000 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.spec.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { SimpleChange } from "@angular/core"; -import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ReactiveFormsModule } from "@angular/forms"; -import { mock, MockProxy } from "jest-mock-extended"; - -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SelectModule, FormFieldModule, BitSubmitDirective } from "@bitwarden/components"; -import { I18nPipe } from "@bitwarden/ui-common"; - -import { ManageTaxInformationComponent } from "./manage-tax-information.component"; - -describe("ManageTaxInformationComponent", () => { - let sut: ManageTaxInformationComponent; - let fixture: ComponentFixture; - let mockTaxService: MockProxy; - - beforeEach(async () => { - mockTaxService = mock(); - await TestBed.configureTestingModule({ - declarations: [ManageTaxInformationComponent], - providers: [ - { provide: TaxServiceAbstraction, useValue: mockTaxService }, - { provide: I18nService, useValue: { t: (key: string) => key } }, - ], - imports: [ - CommonModule, - ReactiveFormsModule, - SelectModule, - FormFieldModule, - BitSubmitDirective, - I18nPipe, - ], - }).compileComponents(); - - fixture = TestBed.createComponent(ManageTaxInformationComponent); - sut = fixture.componentInstance; - fixture.autoDetectChanges(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("creates successfully", () => { - expect(sut).toBeTruthy(); - }); - - it("should initialize with all values empty in startWith", async () => { - // Arrange - sut.startWith = { - country: "", - postalCode: "", - taxId: "", - line1: "", - line2: "", - city: "", - state: "", - }; - - // Act - fixture.detectChanges(); - - // Assert - const startWithValue = sut.startWith; - expect(startWithValue.line1).toHaveLength(0); - expect(startWithValue.line2).toHaveLength(0); - expect(startWithValue.city).toHaveLength(0); - expect(startWithValue.state).toHaveLength(0); - expect(startWithValue.postalCode).toHaveLength(0); - expect(startWithValue.country).toHaveLength(0); - expect(startWithValue.taxId).toHaveLength(0); - }); - - it("should update the tax information protected state when form is updated", async () => { - // Arrange - const line1Value = "123 Street"; - const line2Value = "Apt. 5"; - const cityValue = "New York"; - const stateValue = "NY"; - const countryValue = "USA"; - const postalCodeValue = "123 Street"; - - sut.startWith = { - country: countryValue, - postalCode: "", - taxId: "", - line1: "", - line2: "", - city: "", - state: "", - }; - sut.showTaxIdField = false; - mockTaxService.isCountrySupported.mockResolvedValue(true); - - // Act - await sut.ngOnInit(); - fixture.detectChanges(); - - const line1: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='line1']", - ); - const line2: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='line2']", - ); - const city: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='city']", - ); - const state: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='state']", - ); - const postalCode: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='postalCode']", - ); - - line1.value = line1Value; - line2.value = line2Value; - city.value = cityValue; - state.value = stateValue; - postalCode.value = postalCodeValue; - - line1.dispatchEvent(new Event("input")); - line2.dispatchEvent(new Event("input")); - city.dispatchEvent(new Event("input")); - state.dispatchEvent(new Event("input")); - postalCode.dispatchEvent(new Event("input")); - await fixture.whenStable(); - - // Assert - - // Assert that the internal tax information reflects the form - const taxInformation = sut.getTaxInformation(); - expect(taxInformation.line1).toBe(line1Value); - expect(taxInformation.line2).toBe(line2Value); - expect(taxInformation.city).toBe(cityValue); - expect(taxInformation.state).toBe(stateValue); - expect(taxInformation.postalCode).toBe(postalCodeValue); - expect(taxInformation.country).toBe(countryValue); - expect(taxInformation.taxId).toHaveLength(0); - - expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); - expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(2); - }); - - it("should not show address fields except postal code if country is not supported for taxes", async () => { - // Arrange - const countryValue = "UNKNOWN"; - sut.startWith = { - country: countryValue, - postalCode: "", - taxId: "", - line1: "", - line2: "", - city: "", - state: "", - }; - sut.showTaxIdField = false; - mockTaxService.isCountrySupported.mockResolvedValue(false); - - // Act - await sut.ngOnInit(); - fixture.detectChanges(); - - const line1: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='line1']", - ); - const line2: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='line2']", - ); - const city: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='city']", - ); - const state: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='state']", - ); - const postalCode: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='postalCode']", - ); - - // Assert - expect(line1).toBeNull(); - expect(line2).toBeNull(); - expect(city).toBeNull(); - expect(state).toBeNull(); - //Should be visible - expect(postalCode).toBeTruthy(); - - expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); - expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1); - }); - - it("should not show the tax id field if showTaxIdField is set to false", async () => { - // Arrange - const countryValue = "USA"; - sut.startWith = { - country: countryValue, - postalCode: "", - taxId: "", - line1: "", - line2: "", - city: "", - state: "", - }; - - sut.showTaxIdField = false; - mockTaxService.isCountrySupported.mockResolvedValue(true); - - // Act - await sut.ngOnInit(); - fixture.detectChanges(); - - // Assert - const taxId: HTMLInputElement = fixture.nativeElement.querySelector( - "input[formControlName='taxId']", - ); - expect(taxId).toBeNull(); - - expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); - expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1); - }); - - it("should clear the tax id field if showTaxIdField is set to false after being true", async () => { - // Arrange - const countryValue = "USA"; - const taxIdValue = "A12345678"; - - sut.startWith = { - country: countryValue, - postalCode: "", - taxId: taxIdValue, - line1: "", - line2: "", - city: "", - state: "", - }; - sut.showTaxIdField = true; - - mockTaxService.isCountrySupported.mockResolvedValue(true); - await sut.ngOnInit(); - fixture.detectChanges(); - const initialTaxIdValue = fixture.nativeElement.querySelector( - "input[formControlName='taxId']", - ).value; - - // Act - sut.showTaxIdField = false; - sut.ngOnChanges({ showTaxIdField: new SimpleChange(true, false, false) }); - fixture.detectChanges(); - - // Assert - const taxId = fixture.nativeElement.querySelector("input[formControlName='taxId']"); - expect(taxId).toBeNull(); - - const taxInformation = sut.getTaxInformation(); - expect(taxInformation.taxId).toBeNull(); - expect(initialTaxIdValue).toEqual(taxIdValue); - - expect(mockTaxService.isCountrySupported).toHaveBeenCalledWith(countryValue); - expect(mockTaxService.isCountrySupported).toHaveBeenCalledTimes(1); - }); -}); diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts deleted file mode 100644 index 0b87f3f931d..00000000000 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ /dev/null @@ -1,166 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { - Component, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - SimpleChanges, -} from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; -import { debounceTime } from "rxjs/operators"; - -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; -import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/models/domain"; - -@Component({ - selector: "app-manage-tax-information", - templateUrl: "./manage-tax-information.component.html", - standalone: false, -}) -export class ManageTaxInformationComponent implements OnInit, OnDestroy, OnChanges { - @Input() startWith: TaxInformation; - @Input() onSubmit?: (taxInformation: TaxInformation) => Promise; - @Input() showTaxIdField: boolean = true; - - /** - * Emits when the tax information has changed. - */ - @Output() taxInformationChanged = new EventEmitter(); - - /** - * Emits when the tax information has been updated. - */ - @Output() taxInformationUpdated = new EventEmitter(); - - private taxInformation: TaxInformation; - - protected formGroup = this.formBuilder.group({ - country: ["", Validators.required], - postalCode: ["", Validators.required], - taxId: "", - line1: "", - line2: "", - city: "", - state: "", - }); - - protected isTaxSupported: boolean; - - private destroy$ = new Subject(); - - protected readonly countries: CountryListItem[] = this.taxService.getCountries(); - - constructor( - private formBuilder: FormBuilder, - private taxService: TaxServiceAbstraction, - ) {} - - getTaxInformation(): TaxInformation { - return this.taxInformation; - } - - submit = async () => { - this.markAllAsTouched(); - if (this.formGroup.invalid) { - return; - } - await this.onSubmit?.(this.taxInformation); - this.taxInformationUpdated.emit(); - }; - - validate(): boolean { - this.markAllAsTouched(); - return this.formGroup.valid; - } - - markAllAsTouched() { - this.formGroup.markAllAsTouched(); - } - - async ngOnInit() { - this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((values) => { - this.taxInformation = { - country: values.country, - postalCode: values.postalCode, - taxId: values.taxId, - line1: values.line1, - line2: values.line2, - city: values.city, - state: values.state, - }; - }); - - if (this.startWith) { - this.formGroup.controls.country.setValue(this.startWith.country); - this.formGroup.controls.postalCode.setValue(this.startWith.postalCode); - - this.isTaxSupported = - this.startWith && this.startWith.country - ? await this.taxService.isCountrySupported(this.startWith.country) - : false; - - if (this.isTaxSupported) { - this.formGroup.controls.taxId.setValue(this.startWith.taxId); - this.formGroup.controls.line1.setValue(this.startWith.line1); - this.formGroup.controls.line2.setValue(this.startWith.line2); - this.formGroup.controls.city.setValue(this.startWith.city); - this.formGroup.controls.state.setValue(this.startWith.state); - } - } - - this.formGroup.controls.country.valueChanges - .pipe(debounceTime(1000), takeUntil(this.destroy$)) - .subscribe((country: string) => { - this.taxService - .isCountrySupported(country) - .then((isSupported) => (this.isTaxSupported = isSupported)) - .catch(() => (this.isTaxSupported = false)) - .finally(() => { - if (!this.isTaxSupported) { - this.formGroup.controls.taxId.setValue(null); - this.formGroup.controls.line1.setValue(null); - this.formGroup.controls.line2.setValue(null); - this.formGroup.controls.city.setValue(null); - this.formGroup.controls.state.setValue(null); - } - if (this.taxInformationChanged) { - this.taxInformationChanged.emit(this.taxInformation); - } - }); - }); - - this.formGroup.controls.postalCode.valueChanges - .pipe(debounceTime(1000), takeUntil(this.destroy$)) - .subscribe(() => { - if (this.taxInformationChanged) { - this.taxInformationChanged.emit(this.taxInformation); - } - }); - - this.formGroup.controls.taxId.valueChanges - .pipe(debounceTime(1000), takeUntil(this.destroy$)) - .subscribe(() => { - if (this.taxInformationChanged) { - this.taxInformationChanged.emit(this.taxInformation); - } - }); - } - - ngOnChanges(changes: SimpleChanges): void { - // Clear the value of the tax-id if states have been changed in the parent component - const showTaxIdField = changes["showTaxIdField"]; - if (showTaxIdField && !showTaxIdField.currentValue) { - this.formGroup.controls.taxId.setValue(null); - } - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } -} diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index c0bf1425d47..446530a1111 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -2,12 +2,6 @@ import { CommonModule, DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { - AddAccountCreditDialogComponent, - InvoicesComponent, - NoInvoicesComponent, - ManageTaxInformationComponent, -} from "@bitwarden/angular/billing/components"; import { AsyncActionsModule, AutofocusDirective, @@ -112,10 +106,6 @@ import { IconComponent } from "./vault/components/icon.component"; UserTypePipe, IfFeatureDirective, FingerprintPipe, - AddAccountCreditDialogComponent, - InvoicesComponent, - NoInvoicesComponent, - ManageTaxInformationComponent, TwoFactorIconComponent, ], exports: [ @@ -146,10 +136,6 @@ import { IconComponent } from "./vault/components/icon.component"; UserTypePipe, IfFeatureDirective, FingerprintPipe, - AddAccountCreditDialogComponent, - InvoicesComponent, - NoInvoicesComponent, - ManageTaxInformationComponent, TwoFactorIconComponent, TextDragDirective, ], diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 03d756ee11c..8c727a98d11 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -144,14 +144,12 @@ import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/a import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; import { OrganizationSponsorshipApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction"; -import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { AccountBillingApiService } from "@bitwarden/common/billing/services/account/account-billing-api.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service"; import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service"; import { OrganizationSponsorshipApiService } from "@bitwarden/common/billing/services/organization/organization-sponsorship-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; -import { TaxService } from "@bitwarden/common/billing/services/tax.service"; import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service"; import { DefaultKeyGenerationService, @@ -1398,11 +1396,6 @@ const safeProviders: SafeProvider[] = [ useClass: BillingApiService, deps: [ApiServiceAbstraction], }), - safeProvider({ - provide: TaxServiceAbstraction, - useClass: TaxService, - deps: [ApiServiceAbstraction], - }), safeProvider({ provide: BillingAccountProfileStateService, useClass: DefaultBillingAccountProfileStateService, diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index d746342d728..28ab0613e14 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -77,14 +77,10 @@ import { } from "../auth/models/response/two-factor-web-authn.response"; import { TwoFactorYubiKeyResponse } from "../auth/models/response/two-factor-yubi-key.response"; import { BitPayInvoiceRequest } from "../billing/models/request/bit-pay-invoice.request"; -import { PaymentRequest } from "../billing/models/request/payment.request"; -import { TaxInfoUpdateRequest } from "../billing/models/request/tax-info-update.request"; import { BillingHistoryResponse } from "../billing/models/response/billing-history.response"; -import { BillingPaymentResponse } from "../billing/models/response/billing-payment.response"; import { PaymentResponse } from "../billing/models/response/payment.response"; import { PlanResponse } from "../billing/models/response/plan.response"; import { SubscriptionResponse } from "../billing/models/response/subscription.response"; -import { TaxInfoResponse } from "../billing/models/response/tax-info.response"; import { KeyConnectorUserKeyRequest } from "../key-management/key-connector/models/key-connector-user-key.request"; import { SetKeyConnectorKeyRequest } from "../key-management/key-connector/models/set-key-connector-key.request"; import { DeleteRecoverRequest } from "../models/request/delete-recover.request"; @@ -171,10 +167,8 @@ export abstract class ApiService { abstract getProfile(): Promise; abstract getUserSubscription(): Promise; - abstract getTaxInfo(): Promise; abstract putProfile(request: UpdateProfileRequest): Promise; abstract putAvatar(request: UpdateAvatarRequest): Promise; - abstract putTaxInfo(request: TaxInfoUpdateRequest): Promise; abstract postPrelogin(request: PreloginRequest): Promise; abstract postEmailToken(request: EmailTokenRequest): Promise; abstract postEmail(request: EmailRequest): Promise; @@ -185,7 +179,6 @@ export abstract class ApiService { abstract postPremium(data: FormData): Promise; abstract postReinstatePremium(): Promise; abstract postAccountStorage(request: StorageRequest): Promise; - abstract postAccountPayment(request: PaymentRequest): Promise; abstract postAccountLicense(data: FormData): Promise; abstract postAccountKeys(request: KeysRequest): Promise; abstract postAccountVerifyEmail(): Promise; @@ -209,7 +202,6 @@ export abstract class ApiService { abstract getLastAuthRequest(): Promise; abstract getUserBillingHistory(): Promise; - abstract getUserBillingPayment(): Promise; abstract getCipher(id: string): Promise; abstract getFullCipherDetails(id: string): Promise; diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 10626d6758f..6c91c2ea0cf 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -3,21 +3,17 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; import { ApiKeyResponse } from "../../../auth/models/response/api-key.response"; import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response"; -import { ExpandedTaxInfoUpdateRequest } from "../../../billing/models/request/expanded-tax-info-update.request"; import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/models/request/organization-no-payment-method-create-request"; import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request"; import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; -import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; -import { TaxInfoResponse } from "../../../billing/models/response/tax-info.response"; import { ImportDirectoryRequest } from "../../../models/request/import-directory.request"; import { SeatRequest } from "../../../models/request/seat.request"; import { StorageRequest } from "../../../models/request/storage.request"; -import { VerifyBankRequest } from "../../../models/request/verify-bank.request"; import { ListResponse } from "../../../models/response/list.response"; import { OrganizationApiKeyType } from "../../enums"; import { OrganizationCollectionManagementUpdateRequest } from "../../models/request/organization-collection-management-update.request"; @@ -45,7 +41,6 @@ export abstract class OrganizationApiServiceAbstraction { ): Promise; abstract createLicense(data: FormData): Promise; abstract save(id: string, request: OrganizationUpdateRequest): Promise; - abstract updatePayment(id: string, request: PaymentRequest): Promise; abstract upgrade(id: string, request: OrganizationUpgradeRequest): Promise; abstract updatePasswordManagerSeats( id: string, @@ -57,7 +52,6 @@ export abstract class OrganizationApiServiceAbstraction { ): Promise; abstract updateSeats(id: string, request: SeatRequest): Promise; abstract updateStorage(id: string, request: StorageRequest): Promise; - abstract verifyBank(id: string, request: VerifyBankRequest): Promise; abstract reinstate(id: string): Promise; abstract leave(id: string): Promise; abstract delete(id: string, request: SecretVerificationRequest): Promise; @@ -76,8 +70,6 @@ export abstract class OrganizationApiServiceAbstraction { organizationApiKeyType?: OrganizationApiKeyType, ): Promise>; abstract rotateApiKey(id: string, request: OrganizationApiKeyRequest): Promise; - abstract getTaxInfo(id: string): Promise; - abstract updateTaxInfo(id: string, request: ExpandedTaxInfoUpdateRequest): Promise; abstract getKeys(id: string): Promise; abstract updateKeys( id: string, diff --git a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts index 5c9ea5526a0..001bba11cf4 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts @@ -1,7 +1,19 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ExpandedTaxInfoUpdateRequest } from "../../../../billing/models/request/expanded-tax-info-update.request"; -import { TokenizedPaymentSourceRequest } from "../../../../billing/models/request/tokenized-payment-source.request"; +interface TokenizedPaymentMethod { + type: "bankAccount" | "card" | "payPal"; + token: string; +} + +interface BillingAddress { + country: string; + postalCode: string; + line1: string | null; + line2: string | null; + city: string | null; + state: string | null; + taxId: { code: string; value: string } | null; +} export class ProviderSetupRequest { name: string; @@ -9,6 +21,6 @@ export class ProviderSetupRequest { billingEmail: string; token: string; key: string; - taxInfo: ExpandedTaxInfoUpdateRequest; - paymentSource?: TokenizedPaymentSourceRequest; + paymentMethod: TokenizedPaymentMethod; + billingAddress: BillingAddress; } diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index 598bb2a29db..6a7b71389bb 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -7,21 +7,17 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; import { ApiKeyResponse } from "../../../auth/models/response/api-key.response"; import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response"; -import { ExpandedTaxInfoUpdateRequest } from "../../../billing/models/request/expanded-tax-info-update.request"; import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/models/request/organization-no-payment-method-create-request"; import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request"; import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request"; -import { PaymentRequest } from "../../../billing/models/request/payment.request"; import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request"; import { BillingHistoryResponse } from "../../../billing/models/response/billing-history.response"; import { BillingResponse } from "../../../billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response"; import { PaymentResponse } from "../../../billing/models/response/payment.response"; -import { TaxInfoResponse } from "../../../billing/models/response/tax-info.response"; import { ImportDirectoryRequest } from "../../../models/request/import-directory.request"; import { SeatRequest } from "../../../models/request/seat.request"; import { StorageRequest } from "../../../models/request/storage.request"; -import { VerifyBankRequest } from "../../../models/request/verify-bank.request"; import { ListResponse } from "../../../models/response/list.response"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { OrganizationApiServiceAbstraction } from "../../abstractions/organization/organization-api.service.abstraction"; @@ -143,10 +139,6 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction return data; } - async updatePayment(id: string, request: PaymentRequest): Promise { - return this.apiService.send("POST", "/organizations/" + id + "/payment", request, true, false); - } - async upgrade(id: string, request: OrganizationUpgradeRequest): Promise { const r = await this.apiService.send( "POST", @@ -208,16 +200,6 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction return new PaymentResponse(r); } - async verifyBank(id: string, request: VerifyBankRequest): Promise { - await this.apiService.send( - "POST", - "/organizations/" + id + "/verify-bank", - request, - true, - false, - ); - } - async reinstate(id: string): Promise { return this.apiService.send("POST", "/organizations/" + id + "/reinstate", null, true, false); } @@ -299,16 +281,6 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction return new ApiKeyResponse(r); } - async getTaxInfo(id: string): Promise { - const r = await this.apiService.send("GET", "/organizations/" + id + "/tax", null, true, true); - return new TaxInfoResponse(r); - } - - async updateTaxInfo(id: string, request: ExpandedTaxInfoUpdateRequest): Promise { - // Can't broadcast anything because the response doesn't have content - return this.apiService.send("PUT", "/organizations/" + id + "/tax", request, true, false); - } - async getKeys(id: string): Promise { const r = await this.apiService.send("GET", "/organizations/" + id + "/keys", null, true, true); return new OrganizationKeysResponse(r); diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index 2f3fe9125db..b5695e2e8a0 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -1,19 +1,12 @@ -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; - import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; -import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; -import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; -import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; import { InvoicesResponse } from "../models/response/invoices.response"; -import { PaymentMethodResponse } from "../models/response/payment-method.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export abstract class BillingApiServiceAbstraction { @@ -29,14 +22,10 @@ export abstract class BillingApiServiceAbstraction { request: CreateClientOrganizationRequest, ): Promise; - abstract createSetupIntent(paymentMethodType: PaymentMethodType): Promise; - abstract getOrganizationBillingMetadata( organizationId: string, ): Promise; - abstract getOrganizationPaymentMethod(organizationId: string): Promise; - abstract getPlans(): Promise>; abstract getProviderClientInvoiceReport(providerId: string, invoiceId: string): Promise; @@ -49,44 +38,12 @@ export abstract class BillingApiServiceAbstraction { abstract getProviderSubscription(providerId: string): Promise; - abstract getProviderTaxInformation(providerId: string): Promise; - - abstract updateOrganizationPaymentMethod( - organizationId: string, - request: UpdatePaymentMethodRequest, - ): Promise; - - abstract updateOrganizationTaxInformation( - organizationId: string, - request: ExpandedTaxInfoUpdateRequest, - ): Promise; - abstract updateProviderClientOrganization( providerId: string, organizationId: string, request: UpdateClientOrganizationRequest, ): Promise; - abstract updateProviderPaymentMethod( - providerId: string, - request: UpdatePaymentMethodRequest, - ): Promise; - - abstract updateProviderTaxInformation( - providerId: string, - request: ExpandedTaxInfoUpdateRequest, - ): Promise; - - abstract verifyOrganizationBankAccount( - organizationId: string, - request: VerifyBankAccountRequest, - ): Promise; - - abstract verifyProviderBankAccount( - providerId: string, - request: VerifyBankAccountRequest, - ): Promise; - abstract restartSubscription( organizationId: string, request: OrganizationCreateRequest, diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index 3254787457a..215fabfd955 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -3,7 +3,6 @@ import { UserId } from "@bitwarden/user-core"; import { OrganizationResponse } from "../../admin-console/models/response/organization.response"; import { InitiationPath } from "../../models/request/reference-event.request"; import { PaymentMethodType, PlanType } from "../enums"; -import { PaymentSourceResponse } from "../models/response/payment-source.response"; export type OrganizationInformation = { name: string; @@ -45,8 +44,6 @@ export type SubscriptionInformation = { }; export abstract class OrganizationBillingServiceAbstraction { - abstract getPaymentSource(organizationId: string): Promise; - abstract purchaseSubscription( subscription: SubscriptionInformation, activeUserId: UserId, diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts deleted file mode 100644 index c94fbcba652..00000000000 --- a/libs/common/src/billing/abstractions/tax.service.abstraction.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CountryListItem } from "../models/domain"; -import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; -import { PreviewTaxAmountForOrganizationTrialRequest } from "../models/request/tax"; -import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; - -export abstract class TaxServiceAbstraction { - abstract getCountries(): CountryListItem[]; - - abstract isCountrySupported(country: string): Promise; - - abstract previewIndividualInvoice( - request: PreviewIndividualInvoiceRequest, - ): Promise; - - abstract previewOrganizationInvoice( - request: PreviewOrganizationInvoiceRequest, - ): Promise; - - abstract previewTaxAmountForOrganizationTrial: ( - request: PreviewTaxAmountForOrganizationTrialRequest, - ) => Promise; -} diff --git a/libs/common/src/billing/enums/bitwarden-product-type.enum.ts b/libs/common/src/billing/enums/bitwarden-product-type.enum.ts deleted file mode 100644 index 4389d283c00..00000000000 --- a/libs/common/src/billing/enums/bitwarden-product-type.enum.ts +++ /dev/null @@ -1,6 +0,0 @@ -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum BitwardenProductType { - PasswordManager = 0, - SecretsManager = 1, -} diff --git a/libs/common/src/billing/enums/index.ts b/libs/common/src/billing/enums/index.ts index 1a9f3f8219c..ee8cd1f5948 100644 --- a/libs/common/src/billing/enums/index.ts +++ b/libs/common/src/billing/enums/index.ts @@ -2,7 +2,6 @@ export * from "./payment-method-type.enum"; export * from "./plan-sponsorship-type.enum"; export * from "./plan-type.enum"; export * from "./transaction-type.enum"; -export * from "./bitwarden-product-type.enum"; export * from "./product-tier-type.enum"; export * from "./product-type.enum"; export * from "./plan-interval.enum"; diff --git a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts b/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts deleted file mode 100644 index 83b254ac512..00000000000 --- a/libs/common/src/billing/models/request/expanded-tax-info-update.request.ts +++ /dev/null @@ -1,29 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { TaxInformation } from "../domain/tax-information"; - -import { TaxInfoUpdateRequest } from "./tax-info-update.request"; - -export class ExpandedTaxInfoUpdateRequest extends TaxInfoUpdateRequest { - taxId: string; - line1: string; - line2: string; - city: string; - state: string; - - static From(taxInformation: TaxInformation): ExpandedTaxInfoUpdateRequest { - if (!taxInformation) { - return null; - } - - const request = new ExpandedTaxInfoUpdateRequest(); - request.country = taxInformation.country; - request.postalCode = taxInformation.postalCode; - request.taxId = taxInformation.taxId; - request.line1 = taxInformation.line1; - request.line2 = taxInformation.line2; - request.city = taxInformation.city; - request.state = taxInformation.state; - return request; - } -} diff --git a/libs/common/src/billing/models/request/payment.request.ts b/libs/common/src/billing/models/request/payment.request.ts deleted file mode 100644 index e2edd9aabb3..00000000000 --- a/libs/common/src/billing/models/request/payment.request.ts +++ /dev/null @@ -1,10 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { PaymentMethodType } from "../../enums"; - -import { ExpandedTaxInfoUpdateRequest } from "./expanded-tax-info-update.request"; - -export class PaymentRequest extends ExpandedTaxInfoUpdateRequest { - paymentMethodType: PaymentMethodType; - paymentToken: string; -} diff --git a/libs/common/src/billing/models/request/preview-individual-invoice.request.ts b/libs/common/src/billing/models/request/preview-individual-invoice.request.ts deleted file mode 100644 index f817398c629..00000000000 --- a/libs/common/src/billing/models/request/preview-individual-invoice.request.ts +++ /dev/null @@ -1,28 +0,0 @@ -// @ts-strict-ignore -export class PreviewIndividualInvoiceRequest { - passwordManager: PasswordManager; - taxInformation: TaxInformation; - - constructor(passwordManager: PasswordManager, taxInformation: TaxInformation) { - this.passwordManager = passwordManager; - this.taxInformation = taxInformation; - } -} - -class PasswordManager { - additionalStorage: number; - - constructor(additionalStorage: number) { - this.additionalStorage = additionalStorage; - } -} - -class TaxInformation { - postalCode: string; - country: string; - - constructor(postalCode: string, country: string) { - this.postalCode = postalCode; - this.country = country; - } -} diff --git a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts deleted file mode 100644 index bfeecb4eb23..00000000000 --- a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { PlanSponsorshipType, PlanType } from "../../enums"; - -export class PreviewOrganizationInvoiceRequest { - organizationId?: string; - passwordManager: PasswordManager; - secretsManager?: SecretsManager; - taxInformation: TaxInformation; - - constructor( - passwordManager: PasswordManager, - taxInformation: TaxInformation, - organizationId?: string, - secretsManager?: SecretsManager, - ) { - this.organizationId = organizationId; - this.passwordManager = passwordManager; - this.secretsManager = secretsManager; - this.taxInformation = taxInformation; - } -} - -class PasswordManager { - plan: PlanType; - sponsoredPlan?: PlanSponsorshipType; - seats: number; - additionalStorage: number; - - constructor(plan: PlanType, seats: number, additionalStorage: number) { - this.plan = plan; - this.seats = seats; - this.additionalStorage = additionalStorage; - } -} - -class SecretsManager { - seats: number; - additionalMachineAccounts: number; - - constructor(seats: number, additionalMachineAccounts: number) { - this.seats = seats; - this.additionalMachineAccounts = additionalMachineAccounts; - } -} - -class TaxInformation { - postalCode: string; - country: string; - taxId: string; - - constructor(postalCode: string, country: string, taxId: string) { - this.postalCode = postalCode; - this.country = country; - this.taxId = taxId; - } -} diff --git a/libs/common/src/billing/models/request/tax-info-update.request.ts b/libs/common/src/billing/models/request/tax-info-update.request.ts deleted file mode 100644 index 6f767535472..00000000000 --- a/libs/common/src/billing/models/request/tax-info-update.request.ts +++ /dev/null @@ -1,6 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -export class TaxInfoUpdateRequest { - country: string; - postalCode: string; -} diff --git a/libs/common/src/billing/models/request/tax/index.ts b/libs/common/src/billing/models/request/tax/index.ts deleted file mode 100644 index cda1930c614..00000000000 --- a/libs/common/src/billing/models/request/tax/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./preview-tax-amount-for-organization-trial.request"; diff --git a/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts b/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts deleted file mode 100644 index 3f366335a47..00000000000 --- a/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PlanType, ProductType } from "../../../enums"; - -export type PreviewTaxAmountForOrganizationTrialRequest = { - planType: PlanType; - productType: ProductType; - taxInformation: { - country: string; - postalCode: string; - taxId?: string; - }; -}; diff --git a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts b/libs/common/src/billing/models/request/tokenized-payment-source.request.ts deleted file mode 100644 index e4bf575cc6a..00000000000 --- a/libs/common/src/billing/models/request/tokenized-payment-source.request.ts +++ /dev/null @@ -1,8 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { PaymentMethodType } from "../../enums"; - -export class TokenizedPaymentSourceRequest { - type: PaymentMethodType; - token: string; -} diff --git a/libs/common/src/billing/models/request/update-payment-method.request.ts b/libs/common/src/billing/models/request/update-payment-method.request.ts deleted file mode 100644 index 10b03103716..00000000000 --- a/libs/common/src/billing/models/request/update-payment-method.request.ts +++ /dev/null @@ -1,9 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ExpandedTaxInfoUpdateRequest } from "./expanded-tax-info-update.request"; -import { TokenizedPaymentSourceRequest } from "./tokenized-payment-source.request"; - -export class UpdatePaymentMethodRequest { - paymentSource: TokenizedPaymentSourceRequest; - taxInformation: ExpandedTaxInfoUpdateRequest; -} diff --git a/libs/common/src/billing/models/request/verify-bank-account.request.ts b/libs/common/src/billing/models/request/verify-bank-account.request.ts deleted file mode 100644 index ee85d1a2aad..00000000000 --- a/libs/common/src/billing/models/request/verify-bank-account.request.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class VerifyBankAccountRequest { - descriptorCode: string; - - constructor(descriptorCode: string) { - this.descriptorCode = descriptorCode; - } -} diff --git a/libs/common/src/billing/models/response/billing-payment.response.ts b/libs/common/src/billing/models/response/billing-payment.response.ts deleted file mode 100644 index e60a11c0772..00000000000 --- a/libs/common/src/billing/models/response/billing-payment.response.ts +++ /dev/null @@ -1,17 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { BaseResponse } from "../../../models/response/base.response"; - -import { BillingSourceResponse } from "./billing.response"; - -export class BillingPaymentResponse extends BaseResponse { - balance: number; - paymentSource: BillingSourceResponse; - - constructor(response: any) { - super(response); - this.balance = this.getResponseProperty("Balance"); - const paymentSource = this.getResponseProperty("PaymentSource"); - this.paymentSource = paymentSource == null ? null : new BillingSourceResponse(paymentSource); - } -} diff --git a/libs/common/src/billing/models/response/payment-method.response.ts b/libs/common/src/billing/models/response/payment-method.response.ts deleted file mode 100644 index 34e95032aef..00000000000 --- a/libs/common/src/billing/models/response/payment-method.response.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BaseResponse } from "../../../models/response/base.response"; - -import { PaymentSourceResponse } from "./payment-source.response"; -import { TaxInfoResponse } from "./tax-info.response"; - -export class PaymentMethodResponse extends BaseResponse { - accountCredit: number; - paymentSource?: PaymentSourceResponse; - subscriptionStatus?: string; - taxInformation?: TaxInfoResponse; - - constructor(response: any) { - super(response); - this.accountCredit = this.getResponseProperty("AccountCredit"); - - const paymentSource = this.getResponseProperty("PaymentSource"); - if (paymentSource) { - this.paymentSource = new PaymentSourceResponse(paymentSource); - } - - this.subscriptionStatus = this.getResponseProperty("SubscriptionStatus"); - - const taxInformation = this.getResponseProperty("TaxInformation"); - if (taxInformation) { - this.taxInformation = new TaxInfoResponse(taxInformation); - } - } -} diff --git a/libs/common/src/billing/models/response/tax-id-types.response.ts b/libs/common/src/billing/models/response/tax-id-types.response.ts deleted file mode 100644 index f31f2133b34..00000000000 --- a/libs/common/src/billing/models/response/tax-id-types.response.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BaseResponse } from "../../../models/response/base.response"; - -export class TaxIdTypesResponse extends BaseResponse { - taxIdTypes: TaxIdTypeResponse[] = []; - - constructor(response: any) { - super(response); - const taxIdTypes = this.getResponseProperty("TaxIdTypes"); - if (taxIdTypes && taxIdTypes.length) { - this.taxIdTypes = taxIdTypes.map((t: any) => new TaxIdTypeResponse(t)); - } - } -} - -export class TaxIdTypeResponse extends BaseResponse { - code: string; - country: string; - description: string; - example: string; - - constructor(response: any) { - super(response); - this.code = this.getResponseProperty("Code"); - this.country = this.getResponseProperty("Country"); - this.description = this.getResponseProperty("Description"); - this.example = this.getResponseProperty("Example"); - } -} diff --git a/libs/common/src/billing/models/response/tax/index.ts b/libs/common/src/billing/models/response/tax/index.ts deleted file mode 100644 index 525d6d7c80a..00000000000 --- a/libs/common/src/billing/models/response/tax/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./preview-tax-amount.response"; diff --git a/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts b/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts deleted file mode 100644 index cf15156551a..00000000000 --- a/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseResponse } from "../../../../models/response/base.response"; - -export class PreviewTaxAmountResponse extends BaseResponse { - taxAmount: number; - - constructor(response: any) { - super(response); - - this.taxAmount = this.getResponseProperty("TaxAmount"); - } -} diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 2292f26e616..a34809e9f02 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -1,23 +1,16 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; - import { ApiService } from "../../abstractions/api.service"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { ListResponse } from "../../models/response/list.response"; import { BillingApiServiceAbstraction } from "../abstractions"; -import { PaymentMethodType } from "../enums"; import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; -import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; -import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; import { InvoicesResponse } from "../models/response/invoices.response"; import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response"; -import { PaymentMethodResponse } from "../models/response/payment-method.response"; import { PlanResponse } from "../models/response/plan.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; @@ -54,21 +47,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { ); } - async createSetupIntent(type: PaymentMethodType) { - const getPath = () => { - switch (type) { - case PaymentMethodType.BankAccount: { - return "/setup-intent/bank-account"; - } - case PaymentMethodType.Card: { - return "/setup-intent/card"; - } - } - }; - const response = await this.apiService.send("POST", getPath(), null, true, true); - return response as string; - } - async getOrganizationBillingMetadata( organizationId: string, ): Promise { @@ -83,17 +61,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new OrganizationBillingMetadataResponse(r); } - async getOrganizationPaymentMethod(organizationId: string): Promise { - const response = await this.apiService.send( - "GET", - "/organizations/" + organizationId + "/billing/payment-method", - null, - true, - true, - ); - return new PaymentMethodResponse(response); - } - async getPlans(): Promise> { const r = await this.apiService.send("GET", "/plans", null, false, true); return new ListResponse(r, PlanResponse); @@ -145,43 +112,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new ProviderSubscriptionResponse(response); } - async getProviderTaxInformation(providerId: string): Promise { - const response = await this.apiService.send( - "GET", - "/providers/" + providerId + "/billing/tax-information", - null, - true, - true, - ); - return new TaxInfoResponse(response); - } - - async updateOrganizationPaymentMethod( - organizationId: string, - request: UpdatePaymentMethodRequest, - ): Promise { - return await this.apiService.send( - "PUT", - "/organizations/" + organizationId + "/billing/payment-method", - request, - true, - false, - ); - } - - async updateOrganizationTaxInformation( - organizationId: string, - request: ExpandedTaxInfoUpdateRequest, - ): Promise { - return await this.apiService.send( - "PUT", - "/organizations/" + organizationId + "/billing/tax-information", - request, - true, - false, - ); - } - async updateProviderClientOrganization( providerId: string, organizationId: string, @@ -196,55 +126,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { ); } - async updateProviderPaymentMethod( - providerId: string, - request: UpdatePaymentMethodRequest, - ): Promise { - return await this.apiService.send( - "PUT", - "/providers/" + providerId + "/billing/payment-method", - request, - true, - false, - ); - } - - async updateProviderTaxInformation(providerId: string, request: ExpandedTaxInfoUpdateRequest) { - return await this.apiService.send( - "PUT", - "/providers/" + providerId + "/billing/tax-information", - request, - true, - false, - ); - } - - async verifyOrganizationBankAccount( - organizationId: string, - request: VerifyBankAccountRequest, - ): Promise { - return await this.apiService.send( - "POST", - "/organizations/" + organizationId + "/billing/payment-method/verify-bank-account", - request, - true, - false, - ); - } - - async verifyProviderBankAccount( - providerId: string, - request: VerifyBankAccountRequest, - ): Promise { - return await this.apiService.send( - "POST", - "/providers/" + providerId + "/billing/payment-method/verify-bank-account", - request, - true, - false, - ); - } - async restartSubscription( organizationId: string, request: OrganizationCreateRequest, diff --git a/libs/common/src/billing/services/organization-billing.service.spec.ts b/libs/common/src/billing/services/organization-billing.service.spec.ts index 42cfb4a5371..a14dd0f0279 100644 --- a/libs/common/src/billing/services/organization-billing.service.spec.ts +++ b/libs/common/src/billing/services/organization-billing.service.spec.ts @@ -23,7 +23,6 @@ import { OrganizationResponse } from "../../admin-console/models/response/organi import { EncString } from "../../key-management/crypto/models/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { OrgKey } from "../../types/key"; -import { PaymentMethodResponse } from "../models/response/payment-method.response"; describe("OrganizationBillingService", () => { let apiService: jest.Mocked; @@ -62,47 +61,6 @@ describe("OrganizationBillingService", () => { return jest.resetAllMocks(); }); - describe("getPaymentSource()", () => { - it("given a valid organization id, then it returns a payment source", async () => { - //Arrange - const orgId = "organization-test"; - const paymentMethodResponse = { - paymentSource: { type: PaymentMethodType.Card }, - } as PaymentMethodResponse; - billingApiService.getOrganizationPaymentMethod.mockResolvedValue(paymentMethodResponse); - - //Act - const returnedPaymentSource = await sut.getPaymentSource(orgId); - - //Assert - expect(billingApiService.getOrganizationPaymentMethod).toHaveBeenCalledTimes(1); - expect(returnedPaymentSource).toEqual(paymentMethodResponse.paymentSource); - }); - - it("given an invalid organizationId, it should return undefined", async () => { - //Arrange - const orgId = "invalid-id"; - billingApiService.getOrganizationPaymentMethod.mockResolvedValue(null); - - //Act - const returnedPaymentSource = await sut.getPaymentSource(orgId); - - //Assert - expect(billingApiService.getOrganizationPaymentMethod).toHaveBeenCalledTimes(1); - expect(returnedPaymentSource).toBeUndefined(); - }); - - it("given an API error occurs, then it throws the error", async () => { - // Arrange - const orgId = "error-org"; - billingApiService.getOrganizationPaymentMethod.mockRejectedValue(new Error("API Error")); - - // Act & Assert - await expect(sut.getPaymentSource(orgId)).rejects.toThrow("API Error"); - expect(billingApiService.getOrganizationPaymentMethod).toHaveBeenCalledTimes(1); - }); - }); - describe("purchaseSubscription()", () => { it("given valid subscription information, then it returns successful response", async () => { //Arrange @@ -118,7 +76,7 @@ describe("OrganizationBillingService", () => { const organizationResponse = { name: subscriptionInformation.organization.name, billingEmail: subscriptionInformation.organization.billingEmail, - planType: subscriptionInformation.plan.type, + planType: subscriptionInformation.plan!.type, } as OrganizationResponse; organizationApiService.create.mockResolvedValue(organizationResponse); @@ -201,8 +159,8 @@ describe("OrganizationBillingService", () => { const organizationResponse = { name: subscriptionInformation.organization.name, - plan: { type: subscriptionInformation.plan.type }, - planType: subscriptionInformation.plan.type, + plan: { type: subscriptionInformation.plan!.type }, + planType: subscriptionInformation.plan!.type, } as OrganizationResponse; organizationApiService.createWithoutPayment.mockResolvedValue(organizationResponse); @@ -262,7 +220,7 @@ describe("OrganizationBillingService", () => { const organizationResponse = { name: subscriptionInformation.organization.name, billingEmail: subscriptionInformation.organization.billingEmail, - planType: subscriptionInformation.plan.type, + planType: subscriptionInformation.plan!.type, } as OrganizationResponse; organizationApiService.create.mockResolvedValue(organizationResponse); diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index 53ce727df68..4120047a15f 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -25,7 +25,6 @@ import { } from "../abstractions"; import { PlanType } from "../enums"; import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request"; -import { PaymentSourceResponse } from "../models/response/payment-source.response"; interface OrganizationKeys { encryptedKey: EncString; @@ -45,11 +44,6 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs private syncService: SyncService, ) {} - async getPaymentSource(organizationId: string): Promise { - const paymentMethod = await this.billingApiService.getOrganizationPaymentMethod(organizationId); - return paymentMethod?.paymentSource; - } - async purchaseSubscription( subscription: SubscriptionInformation, activeUserId: UserId, diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts deleted file mode 100644 index 27966016913..00000000000 --- a/libs/common/src/billing/services/tax.service.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { PreviewTaxAmountForOrganizationTrialRequest } from "@bitwarden/common/billing/models/request/tax"; - -import { ApiService } from "../../abstractions/api.service"; -import { TaxServiceAbstraction } from "../abstractions/tax.service.abstraction"; -import { CountryListItem } from "../models/domain"; -import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; -import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; -import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; - -export class TaxService implements TaxServiceAbstraction { - constructor(private apiService: ApiService) {} - - getCountries(): CountryListItem[] { - return [ - { name: "-- Select --", value: "", disabled: false }, - { name: "United States", value: "US", disabled: false }, - { name: "China", value: "CN", disabled: false }, - { name: "France", value: "FR", disabled: false }, - { name: "Germany", value: "DE", disabled: false }, - { name: "Canada", value: "CA", disabled: false }, - { name: "United Kingdom", value: "GB", disabled: false }, - { name: "Australia", value: "AU", disabled: false }, - { name: "India", value: "IN", disabled: false }, - { name: "", value: "-", disabled: true }, - { name: "Afghanistan", value: "AF", disabled: false }, - { name: "Åland Islands", value: "AX", disabled: false }, - { name: "Albania", value: "AL", disabled: false }, - { name: "Algeria", value: "DZ", disabled: false }, - { name: "American Samoa", value: "AS", disabled: false }, - { name: "Andorra", value: "AD", disabled: false }, - { name: "Angola", value: "AO", disabled: false }, - { name: "Anguilla", value: "AI", disabled: false }, - { name: "Antarctica", value: "AQ", disabled: false }, - { name: "Antigua and Barbuda", value: "AG", disabled: false }, - { name: "Argentina", value: "AR", disabled: false }, - { name: "Armenia", value: "AM", disabled: false }, - { name: "Aruba", value: "AW", disabled: false }, - { name: "Austria", value: "AT", disabled: false }, - { name: "Azerbaijan", value: "AZ", disabled: false }, - { name: "Bahamas", value: "BS", disabled: false }, - { name: "Bahrain", value: "BH", disabled: false }, - { name: "Bangladesh", value: "BD", disabled: false }, - { name: "Barbados", value: "BB", disabled: false }, - { name: "Belarus", value: "BY", disabled: false }, - { name: "Belgium", value: "BE", disabled: false }, - { name: "Belize", value: "BZ", disabled: false }, - { name: "Benin", value: "BJ", disabled: false }, - { name: "Bermuda", value: "BM", disabled: false }, - { name: "Bhutan", value: "BT", disabled: false }, - { name: "Bolivia, Plurinational State of", value: "BO", disabled: false }, - { name: "Bonaire, Sint Eustatius and Saba", value: "BQ", disabled: false }, - { name: "Bosnia and Herzegovina", value: "BA", disabled: false }, - { name: "Botswana", value: "BW", disabled: false }, - { name: "Bouvet Island", value: "BV", disabled: false }, - { name: "Brazil", value: "BR", disabled: false }, - { name: "British Indian Ocean Territory", value: "IO", disabled: false }, - { name: "Brunei Darussalam", value: "BN", disabled: false }, - { name: "Bulgaria", value: "BG", disabled: false }, - { name: "Burkina Faso", value: "BF", disabled: false }, - { name: "Burundi", value: "BI", disabled: false }, - { name: "Cambodia", value: "KH", disabled: false }, - { name: "Cameroon", value: "CM", disabled: false }, - { name: "Cape Verde", value: "CV", disabled: false }, - { name: "Cayman Islands", value: "KY", disabled: false }, - { name: "Central African Republic", value: "CF", disabled: false }, - { name: "Chad", value: "TD", disabled: false }, - { name: "Chile", value: "CL", disabled: false }, - { name: "Christmas Island", value: "CX", disabled: false }, - { name: "Cocos (Keeling) Islands", value: "CC", disabled: false }, - { name: "Colombia", value: "CO", disabled: false }, - { name: "Comoros", value: "KM", disabled: false }, - { name: "Congo", value: "CG", disabled: false }, - { name: "Congo, the Democratic Republic of the", value: "CD", disabled: false }, - { name: "Cook Islands", value: "CK", disabled: false }, - { name: "Costa Rica", value: "CR", disabled: false }, - { name: "Côte d'Ivoire", value: "CI", disabled: false }, - { name: "Croatia", value: "HR", disabled: false }, - { name: "Cuba", value: "CU", disabled: false }, - { name: "Curaçao", value: "CW", disabled: false }, - { name: "Cyprus", value: "CY", disabled: false }, - { name: "Czech Republic", value: "CZ", disabled: false }, - { name: "Denmark", value: "DK", disabled: false }, - { name: "Djibouti", value: "DJ", disabled: false }, - { name: "Dominica", value: "DM", disabled: false }, - { name: "Dominican Republic", value: "DO", disabled: false }, - { name: "Ecuador", value: "EC", disabled: false }, - { name: "Egypt", value: "EG", disabled: false }, - { name: "El Salvador", value: "SV", disabled: false }, - { name: "Equatorial Guinea", value: "GQ", disabled: false }, - { name: "Eritrea", value: "ER", disabled: false }, - { name: "Estonia", value: "EE", disabled: false }, - { name: "Ethiopia", value: "ET", disabled: false }, - { name: "Falkland Islands (Malvinas)", value: "FK", disabled: false }, - { name: "Faroe Islands", value: "FO", disabled: false }, - { name: "Fiji", value: "FJ", disabled: false }, - { name: "Finland", value: "FI", disabled: false }, - { name: "French Guiana", value: "GF", disabled: false }, - { name: "French Polynesia", value: "PF", disabled: false }, - { name: "French Southern Territories", value: "TF", disabled: false }, - { name: "Gabon", value: "GA", disabled: false }, - { name: "Gambia", value: "GM", disabled: false }, - { name: "Georgia", value: "GE", disabled: false }, - { name: "Ghana", value: "GH", disabled: false }, - { name: "Gibraltar", value: "GI", disabled: false }, - { name: "Greece", value: "GR", disabled: false }, - { name: "Greenland", value: "GL", disabled: false }, - { name: "Grenada", value: "GD", disabled: false }, - { name: "Guadeloupe", value: "GP", disabled: false }, - { name: "Guam", value: "GU", disabled: false }, - { name: "Guatemala", value: "GT", disabled: false }, - { name: "Guernsey", value: "GG", disabled: false }, - { name: "Guinea", value: "GN", disabled: false }, - { name: "Guinea-Bissau", value: "GW", disabled: false }, - { name: "Guyana", value: "GY", disabled: false }, - { name: "Haiti", value: "HT", disabled: false }, - { name: "Heard Island and McDonald Islands", value: "HM", disabled: false }, - { name: "Holy See (Vatican City State)", value: "VA", disabled: false }, - { name: "Honduras", value: "HN", disabled: false }, - { name: "Hong Kong", value: "HK", disabled: false }, - { name: "Hungary", value: "HU", disabled: false }, - { name: "Iceland", value: "IS", disabled: false }, - { name: "Indonesia", value: "ID", disabled: false }, - { name: "Iran, Islamic Republic of", value: "IR", disabled: false }, - { name: "Iraq", value: "IQ", disabled: false }, - { name: "Ireland", value: "IE", disabled: false }, - { name: "Isle of Man", value: "IM", disabled: false }, - { name: "Israel", value: "IL", disabled: false }, - { name: "Italy", value: "IT", disabled: false }, - { name: "Jamaica", value: "JM", disabled: false }, - { name: "Japan", value: "JP", disabled: false }, - { name: "Jersey", value: "JE", disabled: false }, - { name: "Jordan", value: "JO", disabled: false }, - { name: "Kazakhstan", value: "KZ", disabled: false }, - { name: "Kenya", value: "KE", disabled: false }, - { name: "Kiribati", value: "KI", disabled: false }, - { name: "Korea, Democratic People's Republic of", value: "KP", disabled: false }, - { name: "Korea, Republic of", value: "KR", disabled: false }, - { name: "Kuwait", value: "KW", disabled: false }, - { name: "Kyrgyzstan", value: "KG", disabled: false }, - { name: "Lao People's Democratic Republic", value: "LA", disabled: false }, - { name: "Latvia", value: "LV", disabled: false }, - { name: "Lebanon", value: "LB", disabled: false }, - { name: "Lesotho", value: "LS", disabled: false }, - { name: "Liberia", value: "LR", disabled: false }, - { name: "Libya", value: "LY", disabled: false }, - { name: "Liechtenstein", value: "LI", disabled: false }, - { name: "Lithuania", value: "LT", disabled: false }, - { name: "Luxembourg", value: "LU", disabled: false }, - { name: "Macao", value: "MO", disabled: false }, - { name: "Macedonia, the former Yugoslav Republic of", value: "MK", disabled: false }, - { name: "Madagascar", value: "MG", disabled: false }, - { name: "Malawi", value: "MW", disabled: false }, - { name: "Malaysia", value: "MY", disabled: false }, - { name: "Maldives", value: "MV", disabled: false }, - { name: "Mali", value: "ML", disabled: false }, - { name: "Malta", value: "MT", disabled: false }, - { name: "Marshall Islands", value: "MH", disabled: false }, - { name: "Martinique", value: "MQ", disabled: false }, - { name: "Mauritania", value: "MR", disabled: false }, - { name: "Mauritius", value: "MU", disabled: false }, - { name: "Mayotte", value: "YT", disabled: false }, - { name: "Mexico", value: "MX", disabled: false }, - { name: "Micronesia, Federated States of", value: "FM", disabled: false }, - { name: "Moldova, Republic of", value: "MD", disabled: false }, - { name: "Monaco", value: "MC", disabled: false }, - { name: "Mongolia", value: "MN", disabled: false }, - { name: "Montenegro", value: "ME", disabled: false }, - { name: "Montserrat", value: "MS", disabled: false }, - { name: "Morocco", value: "MA", disabled: false }, - { name: "Mozambique", value: "MZ", disabled: false }, - { name: "Myanmar", value: "MM", disabled: false }, - { name: "Namibia", value: "NA", disabled: false }, - { name: "Nauru", value: "NR", disabled: false }, - { name: "Nepal", value: "NP", disabled: false }, - { name: "Netherlands", value: "NL", disabled: false }, - { name: "New Caledonia", value: "NC", disabled: false }, - { name: "New Zealand", value: "NZ", disabled: false }, - { name: "Nicaragua", value: "NI", disabled: false }, - { name: "Niger", value: "NE", disabled: false }, - { name: "Nigeria", value: "NG", disabled: false }, - { name: "Niue", value: "NU", disabled: false }, - { name: "Norfolk Island", value: "NF", disabled: false }, - { name: "Northern Mariana Islands", value: "MP", disabled: false }, - { name: "Norway", value: "NO", disabled: false }, - { name: "Oman", value: "OM", disabled: false }, - { name: "Pakistan", value: "PK", disabled: false }, - { name: "Palau", value: "PW", disabled: false }, - { name: "Palestinian Territory, Occupied", value: "PS", disabled: false }, - { name: "Panama", value: "PA", disabled: false }, - { name: "Papua New Guinea", value: "PG", disabled: false }, - { name: "Paraguay", value: "PY", disabled: false }, - { name: "Peru", value: "PE", disabled: false }, - { name: "Philippines", value: "PH", disabled: false }, - { name: "Pitcairn", value: "PN", disabled: false }, - { name: "Poland", value: "PL", disabled: false }, - { name: "Portugal", value: "PT", disabled: false }, - { name: "Puerto Rico", value: "PR", disabled: false }, - { name: "Qatar", value: "QA", disabled: false }, - { name: "Réunion", value: "RE", disabled: false }, - { name: "Romania", value: "RO", disabled: false }, - { name: "Russian Federation", value: "RU", disabled: false }, - { name: "Rwanda", value: "RW", disabled: false }, - { name: "Saint Barthélemy", value: "BL", disabled: false }, - { name: "Saint Helena, Ascension and Tristan da Cunha", value: "SH", disabled: false }, - { name: "Saint Kitts and Nevis", value: "KN", disabled: false }, - { name: "Saint Lucia", value: "LC", disabled: false }, - { name: "Saint Martin (French part)", value: "MF", disabled: false }, - { name: "Saint Pierre and Miquelon", value: "PM", disabled: false }, - { name: "Saint Vincent and the Grenadines", value: "VC", disabled: false }, - { name: "Samoa", value: "WS", disabled: false }, - { name: "San Marino", value: "SM", disabled: false }, - { name: "Sao Tome and Principe", value: "ST", disabled: false }, - { name: "Saudi Arabia", value: "SA", disabled: false }, - { name: "Senegal", value: "SN", disabled: false }, - { name: "Serbia", value: "RS", disabled: false }, - { name: "Seychelles", value: "SC", disabled: false }, - { name: "Sierra Leone", value: "SL", disabled: false }, - { name: "Singapore", value: "SG", disabled: false }, - { name: "Sint Maarten (Dutch part)", value: "SX", disabled: false }, - { name: "Slovakia", value: "SK", disabled: false }, - { name: "Slovenia", value: "SI", disabled: false }, - { name: "Solomon Islands", value: "SB", disabled: false }, - { name: "Somalia", value: "SO", disabled: false }, - { name: "South Africa", value: "ZA", disabled: false }, - { name: "South Georgia and the South Sandwich Islands", value: "GS", disabled: false }, - { name: "South Sudan", value: "SS", disabled: false }, - { name: "Spain", value: "ES", disabled: false }, - { name: "Sri Lanka", value: "LK", disabled: false }, - { name: "Sudan", value: "SD", disabled: false }, - { name: "Suriname", value: "SR", disabled: false }, - { name: "Svalbard and Jan Mayen", value: "SJ", disabled: false }, - { name: "Swaziland", value: "SZ", disabled: false }, - { name: "Sweden", value: "SE", disabled: false }, - { name: "Switzerland", value: "CH", disabled: false }, - { name: "Syrian Arab Republic", value: "SY", disabled: false }, - { name: "Taiwan", value: "TW", disabled: false }, - { name: "Tajikistan", value: "TJ", disabled: false }, - { name: "Tanzania, United Republic of", value: "TZ", disabled: false }, - { name: "Thailand", value: "TH", disabled: false }, - { name: "Timor-Leste", value: "TL", disabled: false }, - { name: "Togo", value: "TG", disabled: false }, - { name: "Tokelau", value: "TK", disabled: false }, - { name: "Tonga", value: "TO", disabled: false }, - { name: "Trinidad and Tobago", value: "TT", disabled: false }, - { name: "Tunisia", value: "TN", disabled: false }, - { name: "Turkey", value: "TR", disabled: false }, - { name: "Turkmenistan", value: "TM", disabled: false }, - { name: "Turks and Caicos Islands", value: "TC", disabled: false }, - { name: "Tuvalu", value: "TV", disabled: false }, - { name: "Uganda", value: "UG", disabled: false }, - { name: "Ukraine", value: "UA", disabled: false }, - { name: "United Arab Emirates", value: "AE", disabled: false }, - { name: "United States Minor Outlying Islands", value: "UM", disabled: false }, - { name: "Uruguay", value: "UY", disabled: false }, - { name: "Uzbekistan", value: "UZ", disabled: false }, - { name: "Vanuatu", value: "VU", disabled: false }, - { name: "Venezuela, Bolivarian Republic of", value: "VE", disabled: false }, - { name: "Viet Nam", value: "VN", disabled: false }, - { name: "Virgin Islands, British", value: "VG", disabled: false }, - { name: "Virgin Islands, U.S.", value: "VI", disabled: false }, - { name: "Wallis and Futuna", value: "WF", disabled: false }, - { name: "Western Sahara", value: "EH", disabled: false }, - { name: "Yemen", value: "YE", disabled: false }, - { name: "Zambia", value: "ZM", disabled: false }, - { name: "Zimbabwe", value: "ZW", disabled: false }, - ]; - } - - async isCountrySupported(country: string): Promise { - const response = await this.apiService.send( - "GET", - "/tax/is-country-supported?country=" + country, - null, - true, - true, - ); - return response; - } - - async previewIndividualInvoice( - request: PreviewIndividualInvoiceRequest, - ): Promise { - const response = await this.apiService.send( - "POST", - "/accounts/billing/preview-invoice", - request, - true, - true, - ); - return new PreviewInvoiceResponse(response); - } - - async previewOrganizationInvoice( - request: PreviewOrganizationInvoiceRequest, - ): Promise { - const response = await this.apiService.send( - "POST", - `/invoices/preview-organization`, - request, - true, - true, - ); - return new PreviewInvoiceResponse(response); - } - - async previewTaxAmountForOrganizationTrial( - request: PreviewTaxAmountForOrganizationTrialRequest, - ): Promise { - const response = await this.apiService.send( - "POST", - "/tax/preview-amount/organization-trial", - request, - true, - true, - ); - return response as number; - } -} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 67836befd7c..578d09c9aea 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -23,7 +23,6 @@ export enum FeatureFlag { /* Billing */ TrialPaymentOptional = "PM-8163-trial-payment", PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships", - PM21881_ManagePaymentDetailsOutsideCheckout = "pm-21881-manage-payment-details-outside-checkout", PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover", PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings", @@ -100,7 +99,6 @@ export const DefaultFeatureFlagValue = { /* Billing */ [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.PM17772_AdminInitiatedSponsorships]: FALSE, - [FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout]: FALSE, [FeatureFlag.PM21821_ProviderPortalTakeover]: FALSE, [FeatureFlag.PM22415_TaxIDWarnings]: FALSE, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 70ba76fe797..b10df69e277 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -90,14 +90,10 @@ import { } from "../auth/models/response/two-factor-web-authn.response"; import { TwoFactorYubiKeyResponse } from "../auth/models/response/two-factor-yubi-key.response"; import { BitPayInvoiceRequest } from "../billing/models/request/bit-pay-invoice.request"; -import { PaymentRequest } from "../billing/models/request/payment.request"; -import { TaxInfoUpdateRequest } from "../billing/models/request/tax-info-update.request"; import { BillingHistoryResponse } from "../billing/models/response/billing-history.response"; -import { BillingPaymentResponse } from "../billing/models/response/billing-payment.response"; import { PaymentResponse } from "../billing/models/response/payment.response"; import { PlanResponse } from "../billing/models/response/plan.response"; import { SubscriptionResponse } from "../billing/models/response/subscription.response"; -import { TaxInfoResponse } from "../billing/models/response/tax-info.response"; import { ClientType, DeviceType } from "../enums"; import { KeyConnectorUserKeyRequest } from "../key-management/key-connector/models/key-connector-user-key.request"; import { SetKeyConnectorKeyRequest } from "../key-management/key-connector/models/set-key-connector-key.request"; @@ -294,11 +290,6 @@ export class ApiService implements ApiServiceAbstraction { return new SubscriptionResponse(r); } - async getTaxInfo(): Promise { - const r = await this.send("GET", "/accounts/tax", null, true, true); - return new TaxInfoResponse(r); - } - async putProfile(request: UpdateProfileRequest): Promise { const r = await this.send("PUT", "/accounts/profile", request, true, true); return new ProfileResponse(r); @@ -309,10 +300,6 @@ export class ApiService implements ApiServiceAbstraction { return new ProfileResponse(r); } - putTaxInfo(request: TaxInfoUpdateRequest): Promise { - return this.send("PUT", "/accounts/tax", request, true, false); - } - async postPrelogin(request: PreloginRequest): Promise { const env = await firstValueFrom(this.environmentService.environment$); const r = await this.send( @@ -365,10 +352,6 @@ export class ApiService implements ApiServiceAbstraction { return new PaymentResponse(r); } - postAccountPayment(request: PaymentRequest): Promise { - return this.send("POST", "/accounts/payment", request, true, false); - } - postAccountLicense(data: FormData): Promise { return this.send("POST", "/accounts/license", data, true, false); } @@ -429,11 +412,6 @@ export class ApiService implements ApiServiceAbstraction { return new BillingHistoryResponse(r); } - async getUserBillingPayment(): Promise { - const r = await this.send("GET", "/accounts/billing/payment-method", null, true, true); - return new BillingPaymentResponse(r); - } - // Cipher APIs async getCipher(id: string): Promise { From 7b94d6ab2b35ba65f294454daa6de7901be81015 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:28:47 -0500 Subject: [PATCH 19/83] [PM-18717] Fix multiple organization situation for Free Families Policy sponsorship (#16611) * Resolve multiple org situation * Fix multi org policy mismatch issue --- .../services/free-families-policy.service.ts | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/apps/web/src/app/billing/services/free-families-policy.service.ts b/apps/web/src/app/billing/services/free-families-policy.service.ts index 7a8e3804b2c..52041936e50 100644 --- a/apps/web/src/app/billing/services/free-families-policy.service.ts +++ b/apps/web/src/app/billing/services/free-families-policy.service.ts @@ -19,12 +19,6 @@ interface EnterpriseOrgStatus { @Injectable({ providedIn: "root" }) export class FreeFamiliesPolicyService { - protected enterpriseOrgStatus: EnterpriseOrgStatus = { - isFreeFamilyPolicyEnabled: false, - belongToOneEnterpriseOrgs: false, - belongToMultipleEnterpriseOrgs: false, - }; - constructor( private policyService: PolicyService, private organizationService: OrganizationService, @@ -104,9 +98,11 @@ export class FreeFamiliesPolicyService { if (!orgStatus) { return false; } - const { belongToOneEnterpriseOrgs, isFreeFamilyPolicyEnabled } = orgStatus; + const { isFreeFamilyPolicyEnabled } = orgStatus; const hasSponsorshipOrgs = organizations.some((org) => org.canManageSponsorships); - return hasSponsorshipOrgs && !(belongToOneEnterpriseOrgs && isFreeFamilyPolicyEnabled); + + // Hide if ANY organization has the policy enabled + return hasSponsorshipOrgs && !isFreeFamilyPolicyEnabled; } checkEnterpriseOrganizationsAndFetchPolicy(): Observable { @@ -122,16 +118,12 @@ export class FreeFamiliesPolicyService { const { belongToOneEnterpriseOrgs, belongToMultipleEnterpriseOrgs } = this.evaluateEnterpriseOrganizations(organizations); - if (!belongToOneEnterpriseOrgs) { - return of({ - isFreeFamilyPolicyEnabled: false, - belongToOneEnterpriseOrgs, - belongToMultipleEnterpriseOrgs, - }); - } + // Get all enterprise organization IDs + const enterpriseOrgIds = organizations + .filter((org) => org.canManageSponsorships) + .map((org) => org.id); - const organizationId = this.getOrganizationIdForOneEnterprise(organizations); - if (!organizationId) { + if (enterpriseOrgIds.length === 0) { return of({ isFreeFamilyPolicyEnabled: false, belongToOneEnterpriseOrgs, @@ -145,8 +137,8 @@ export class FreeFamiliesPolicyService { this.policyService.policiesByType$(PolicyType.FreeFamiliesSponsorshipPolicy, userId), ), map((policies) => ({ - isFreeFamilyPolicyEnabled: policies.some( - (policy) => policy.organizationId === organizationId && policy.enabled, + isFreeFamilyPolicyEnabled: enterpriseOrgIds.every((orgId) => + policies.some((policy) => policy.organizationId === orgId && policy.enabled), ), belongToOneEnterpriseOrgs, belongToMultipleEnterpriseOrgs, @@ -166,9 +158,4 @@ export class FreeFamiliesPolicyService { belongToMultipleEnterpriseOrgs: count > 1, }; } - - private getOrganizationIdForOneEnterprise(organizations: any[]): string | null { - const enterpriseOrganizations = organizations.filter((org) => org.canManageSponsorships); - return enterpriseOrganizations.length === 1 ? enterpriseOrganizations[0].id : null; - } } From de3759fa85cfb1010d80118e565a8e55b2532188 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:54:33 -0700 Subject: [PATCH 20/83] refactor(sso-config-tweaks): [Auth/PM-933] Tweaks to SSO Config Page (#16374) Makes some tweaks to the SSO config page: - SSO Identifier: update hint text - Single Sign-On Service URL: make required, remove hint text - Client Secret: make hidden by default (add view/hide toggle) --- apps/web/src/locales/en/messages.json | 10 +++++++--- .../bit-web/src/app/auth/sso/sso.component.html | 14 ++++++++++---- .../bit-web/src/app/auth/sso/sso.component.ts | 4 +++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 4bb4c8873ee..35f369aa647 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5219,9 +5219,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html index ef8241b534c..6d2836ee0ba 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.html @@ -30,8 +30,8 @@ {{ "ssoIdentifier" | i18n }} - {{ "ssoIdentifierHintPartOne" | i18n }} - {{ "claimedDomains" | i18n }} + {{ "ssoIdentifierHint" | i18n }} + {{ "claimedDomainsLearnMore" | i18n }} @@ -209,7 +209,14 @@ {{ "clientSecret" | i18n }} - + + @@ -488,7 +495,6 @@ formControlName="idpSingleSignOnServiceUrl" appInputStripSpaces /> - {{ "idpSingleSignOnServiceUrlRequired" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts index 9baeaabb33f..f68e35bf240 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts @@ -121,6 +121,8 @@ export class SsoComponent implements OnInit, OnDestroy { spMetadataUrl: string; spAcsUrl: string; + showClientSecret = false; + protected openIdForm = this.formBuilder.group>( { authority: new FormControl("", Validators.required), @@ -156,7 +158,7 @@ export class SsoComponent implements OnInit, OnDestroy { idpEntityId: new FormControl("", Validators.required), idpBindingType: new FormControl(Saml2BindingType.HttpRedirect), - idpSingleSignOnServiceUrl: new FormControl(), + idpSingleSignOnServiceUrl: new FormControl("", Validators.required), idpSingleLogoutServiceUrl: new FormControl(), idpX509PublicCert: new FormControl("", Validators.required), idpOutboundSigningAlgorithm: new FormControl(defaultSigningAlgorithm), From 08a022fa520241e3eff5c3cbfd71051b50765996 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Wed, 1 Oct 2025 14:01:53 -0400 Subject: [PATCH 21/83] [CL-227] Tooltip component (#16442) * add tooltip component * fix typescript errors * fix more typescript errors * remove css comments * fix tooltip blocking mouse events * move default position logic to shared util * fix tooltip stories options * add tooltip spec * add offset arg to default positions * add shadow to tooltip * increase offset * adding max width * fix disabled button cursor * add stronger position type * fixing types * change get positions function to type return correctly * more fixing types * default options object * add mock to tooltip stories * add figma link to story * update positions file name. remove getter * remove standalone. add comment about component use * add jsdoc comment to directive inputs * fix typo * remove instances of setInput * fix storybook injection error * remove unneeded functions * remove unneeded variables * remove comment * move popover positions back with component * fix popover i18n mock * creat etooltip positions file * update test to account for change to setInput calls * remove panel class as it's not necessary * improve tooltip docs page * use classes for styling. Simpliy position changes * simplify tests. No longer need to track position changes * move comment to correct place * fix typos * remove unnecessary standalone declaration --- .../src/icon-button/icon-button.component.ts | 6 +- .../components/src/popover/popover.stories.ts | 1 + libs/components/src/tooltip/index.ts | 1 + .../src/tooltip/tooltip-positions.ts | 61 +++++++ .../src/tooltip/tooltip.component.css | 132 +++++++++++++++ .../src/tooltip/tooltip.component.html | 10 ++ .../src/tooltip/tooltip.component.ts | 36 +++++ .../src/tooltip/tooltip.directive.ts | 110 +++++++++++++ libs/components/src/tooltip/tooltip.mdx | 31 ++++ libs/components/src/tooltip/tooltip.spec.ts | 103 ++++++++++++ .../components/src/tooltip/tooltip.stories.ts | 153 ++++++++++++++++++ libs/components/src/tw-theme.css | 1 + 12 files changed, 644 insertions(+), 1 deletion(-) create mode 100644 libs/components/src/tooltip/index.ts create mode 100644 libs/components/src/tooltip/tooltip-positions.ts create mode 100644 libs/components/src/tooltip/tooltip.component.css create mode 100644 libs/components/src/tooltip/tooltip.component.html create mode 100644 libs/components/src/tooltip/tooltip.component.ts create mode 100644 libs/components/src/tooltip/tooltip.directive.ts create mode 100644 libs/components/src/tooltip/tooltip.mdx create mode 100644 libs/components/src/tooltip/tooltip.spec.ts create mode 100644 libs/components/src/tooltip/tooltip.stories.ts diff --git a/libs/components/src/icon-button/icon-button.component.ts b/libs/components/src/icon-button/icon-button.component.ts index 6bb6ccf10bd..d712d5cb2b8 100644 --- a/libs/components/src/icon-button/icon-button.component.ts +++ b/libs/components/src/icon-button/icon-button.component.ts @@ -130,7 +130,11 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE .concat(sizes[this.size()]) .concat( this.showDisabledStyles() || this.disabled() - ? ["aria-disabled:tw-opacity-60", "aria-disabled:hover:!tw-bg-transparent"] + ? [ + "aria-disabled:tw-opacity-60", + "aria-disabled:hover:!tw-bg-transparent", + "tw-cursor-default", + ] : [], ); } diff --git a/libs/components/src/popover/popover.stories.ts b/libs/components/src/popover/popover.stories.ts index 6d2cf77e47d..596381d0777 100644 --- a/libs/components/src/popover/popover.stories.ts +++ b/libs/components/src/popover/popover.stories.ts @@ -23,6 +23,7 @@ export default { useFactory: () => { return new I18nMockService({ close: "Close", + loading: "Loading", }); }, }, diff --git a/libs/components/src/tooltip/index.ts b/libs/components/src/tooltip/index.ts new file mode 100644 index 00000000000..28c35fd6ee6 --- /dev/null +++ b/libs/components/src/tooltip/index.ts @@ -0,0 +1 @@ +export * from "./tooltip.directive"; diff --git a/libs/components/src/tooltip/tooltip-positions.ts b/libs/components/src/tooltip/tooltip-positions.ts new file mode 100644 index 00000000000..6396bb6632e --- /dev/null +++ b/libs/components/src/tooltip/tooltip-positions.ts @@ -0,0 +1,61 @@ +import { ConnectedPosition } from "@angular/cdk/overlay"; + +const ORIGIN_OFFSET_PX = 10; + +export type TooltipPositionIdentifier = + | "right-center" + | "left-center" + | "below-center" + | "above-center"; + +export interface TooltipPosition extends ConnectedPosition { + id: TooltipPositionIdentifier; +} + +export const tooltipPositions: TooltipPosition[] = [ + /** + * The order of these positions matters. The Tooltip component will use + * the first position that fits within the viewport. + */ + + // Tooltip opens to right of trigger + { + id: "right-center", + offsetX: ORIGIN_OFFSET_PX, + originX: "end", + originY: "center", + overlayX: "start", + overlayY: "center", + panelClass: ["bit-tooltip-right-center"], + }, + // ... to left of trigger + { + id: "left-center", + offsetX: -ORIGIN_OFFSET_PX, + originX: "start", + originY: "center", + overlayX: "end", + overlayY: "center", + panelClass: ["bit-tooltip-left-center"], + }, + // ... below trigger + { + id: "below-center", + offsetY: ORIGIN_OFFSET_PX, + originX: "center", + originY: "bottom", + overlayX: "center", + overlayY: "top", + panelClass: ["bit-tooltip-below-center"], + }, + // ... above trigger + { + id: "above-center", + offsetY: -ORIGIN_OFFSET_PX, + originX: "center", + originY: "top", + overlayX: "center", + overlayY: "bottom", + panelClass: ["bit-tooltip-above-center"], + }, +]; diff --git a/libs/components/src/tooltip/tooltip.component.css b/libs/components/src/tooltip/tooltip.component.css new file mode 100644 index 00000000000..4abb9908f25 --- /dev/null +++ b/libs/components/src/tooltip/tooltip.component.css @@ -0,0 +1,132 @@ +:root { + --tooltip-shadow: rgb(0 0 0 / 0.1); +} + +.cdk-overlay-pane:has(.bit-tooltip[data-visible="false"]) { + pointer-events: none; +} + +.bit-tooltip-container { + position: relative; + max-width: 12rem; + opacity: 0; + width: max-content; + box-shadow: + 0 4px 6px -1px var(--tooltip-shadow), + 0 2px 4px -2px var(--tooltip-shadow); + border-radius: 0.75rem; + transition: + transform 100ms ease-in-out, + opacity 100ms ease-in-out; + transform: scale(0.95); + z-index: 1; + + &::before, + &::after { + content: ""; + position: absolute; + width: 1rem; + height: 1rem; + z-index: 1; + rotate: 45deg; + border-radius: 3px; + } + + &::before { + background: linear-gradient(135deg, transparent 50%, rgb(var(--color-text-main)) 50%); + z-index: -1; + } + + &::after { + background: rgb(var(--color-text-main)); + z-index: -1; + } + + &[data-visible="true"] { + opacity: 1; + transform: scale(1); + z-index: 1000; + } + + .bit-tooltip-above-center &, + .bit-tooltip-below-center & { + &::before, + &::after { + inset-inline-start: 50%; + transform: translateX(-50%); + transform-origin: left; + } + } + + .bit-tooltip-above-center & { + &::after { + filter: drop-shadow(0 3px 5px var(--tooltip-shadow)) + drop-shadow(0 1px 3px var(--tooltip-shadow)); + } + + &::before, + &::after { + inset-block-end: -0.25rem; + } + } + + .bit-tooltip-below-center & { + &::after { + display: none; + } + + &::after, + &::before { + inset-block-start: -0.25rem; + rotate: -135deg; + } + } + + .bit-tooltip-left-center &, + .bit-tooltip-right-center & { + &::after, + &::before { + inset-block-start: 50%; + transform: translateY(-50%); + transform-origin: top; + } + } + + .bit-tooltip-left-center & { + &::after { + filter: drop-shadow(-3px 1px 3px var(--tooltip-shadow)) + drop-shadow(-1px 2px 3px var(--tooltip-shadow)); + } + + &::after, + &::before { + inset-inline-end: -0.25rem; + rotate: -45deg; + } + } + + .bit-tooltip-right-center & { + &::after { + filter: drop-shadow(2px -4px 2px var(--tooltip-shadow)) + drop-shadow(0 -1px 3px var(--tooltip-shadow)); + } + + &::after, + &::before { + inset-inline-start: -0.25rem; + rotate: 135deg; + } + } +} + +.bit-tooltip { + width: max-content; + max-width: 12rem; + background-color: rgb(var(--color-text-main)); + color: rgb(var(--color-text-contrast)); + padding: 0.5rem 0.75rem; + border-radius: 0.75rem; + font-size: 0.875rem; + line-height: 1.25rem; + z-index: 2; +} diff --git a/libs/components/src/tooltip/tooltip.component.html b/libs/components/src/tooltip/tooltip.component.html new file mode 100644 index 00000000000..c75cd5fb0d4 --- /dev/null +++ b/libs/components/src/tooltip/tooltip.component.html @@ -0,0 +1,10 @@ + +
+ +
diff --git a/libs/components/src/tooltip/tooltip.component.ts b/libs/components/src/tooltip/tooltip.component.ts new file mode 100644 index 00000000000..6b240507311 --- /dev/null +++ b/libs/components/src/tooltip/tooltip.component.ts @@ -0,0 +1,36 @@ +import { CommonModule } from "@angular/common"; +import { + Component, + ElementRef, + inject, + InjectionToken, + Signal, + TemplateRef, + viewChild, +} from "@angular/core"; + +import { TooltipPosition } from "./tooltip-positions"; + +type TooltipData = { + content: Signal; + isVisible: Signal; + tooltipPosition: Signal; +}; + +export const TOOLTIP_DATA = new InjectionToken("TOOLTIP_DATA"); + +/** + * tooltip component used internally by the tooltip.directive. Not meant to be used explicitly + */ +@Component({ + selector: "bit-tooltip", + templateUrl: "./tooltip.component.html", + imports: [CommonModule], +}) +export class TooltipComponent { + readonly templateRef = viewChild.required(TemplateRef); + + private elementRef = inject(ElementRef); + + readonly tooltipData = inject(TOOLTIP_DATA); +} diff --git a/libs/components/src/tooltip/tooltip.directive.ts b/libs/components/src/tooltip/tooltip.directive.ts new file mode 100644 index 00000000000..153fecfe7bf --- /dev/null +++ b/libs/components/src/tooltip/tooltip.directive.ts @@ -0,0 +1,110 @@ +import { Overlay, OverlayConfig, OverlayRef } from "@angular/cdk/overlay"; +import { ComponentPortal } from "@angular/cdk/portal"; +import { + Directive, + ViewContainerRef, + inject, + OnInit, + ElementRef, + Injector, + input, + effect, + signal, +} from "@angular/core"; + +import { TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions"; +import { TooltipComponent, TOOLTIP_DATA } from "./tooltip.component"; + +/** + * Directive to add a tooltip to any element. The tooltip content is provided via the `bitTooltip` input. + * The position of the tooltip can be set via the `tooltipPosition` input. Default position is "above-center". + */ +@Directive({ + selector: "[bitTooltip]", + host: { + "(mouseenter)": "showTooltip()", + "(mouseleave)": "hideTooltip()", + "(focus)": "showTooltip()", + "(blur)": "hideTooltip()", + }, +}) +export class TooltipDirective implements OnInit { + /** + * The value of this input is forwarded to the tooltip.component to render + */ + readonly bitTooltip = input.required(); + /** + * The value of this input is forwarded to the tooltip.component to set its position explicitly. + * @default "above-center" + */ + readonly tooltipPosition = input("above-center"); + + private isVisible = signal(false); + private overlayRef: OverlayRef | undefined; + private elementRef = inject(ElementRef); + private overlay = inject(Overlay); + private viewContainerRef = inject(ViewContainerRef); + private injector = inject(Injector); + private positionStrategy = this.overlay + .position() + .flexibleConnectedTo(this.elementRef) + .withFlexibleDimensions(false) + .withPush(true); + + private tooltipPortal = new ComponentPortal( + TooltipComponent, + this.viewContainerRef, + Injector.create({ + providers: [ + { + provide: TOOLTIP_DATA, + useValue: { + content: this.bitTooltip, + isVisible: this.isVisible, + tooltipPosition: this.tooltipPosition, + }, + }, + ], + }), + ); + + private showTooltip = () => { + this.isVisible.set(true); + }; + + private hideTooltip = () => { + this.isVisible.set(false); + }; + + private computePositions(tooltipPosition: TooltipPositionIdentifier) { + const chosenPosition = tooltipPositions.find((position) => position.id === tooltipPosition); + + return chosenPosition ? [chosenPosition, ...tooltipPositions] : tooltipPositions; + } + + get defaultPopoverConfig(): OverlayConfig { + return { + hasBackdrop: false, + scrollStrategy: this.overlay.scrollStrategies.reposition(), + }; + } + + ngOnInit() { + this.positionStrategy.withPositions(this.computePositions(this.tooltipPosition())); + + this.overlayRef = this.overlay.create({ + ...this.defaultPopoverConfig, + positionStrategy: this.positionStrategy, + }); + + this.overlayRef.attach(this.tooltipPortal); + + effect( + () => { + this.positionStrategy.withPositions(this.computePositions(this.tooltipPosition())); + this.overlayRef?.updatePosition(); + }, + { injector: this.injector }, + ); + } +} diff --git a/libs/components/src/tooltip/tooltip.mdx b/libs/components/src/tooltip/tooltip.mdx new file mode 100644 index 00000000000..4b6f10d97f8 --- /dev/null +++ b/libs/components/src/tooltip/tooltip.mdx @@ -0,0 +1,31 @@ +import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs"; + +import * as stories from "./tooltip.stories"; + + + +```ts +import { TooltipDirective } from "@bitwarden/components"; +``` + + +<Description /> + +NOTE: The `TooltipComponent` can't be used on its own. It must be applied via the `TooltipDirective` + +<Primary /> +<Controls /> + +## Stories + +### All available positions + +<Canvas of={stories.AllPositions} /> + +### Used with a long content + +<Canvas of={stories.LongContent} /> + +### On disabled element + +<Canvas of={stories.OnDisabledButton} /> diff --git a/libs/components/src/tooltip/tooltip.spec.ts b/libs/components/src/tooltip/tooltip.spec.ts new file mode 100644 index 00000000000..57e05e4f65f --- /dev/null +++ b/libs/components/src/tooltip/tooltip.spec.ts @@ -0,0 +1,103 @@ +import { + ConnectedOverlayPositionChange, + ConnectionPositionPair, + OverlayConfig, + Overlay, +} from "@angular/cdk/overlay"; +import { ComponentPortal } from "@angular/cdk/portal"; +import { Component } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { Observable, Subject } from "rxjs"; + +import { TooltipDirective } from "./tooltip.directive"; + +@Component({ + standalone: true, + imports: [TooltipDirective], + template: ` <button [bitTooltip]="tooltipText" type="button">Hover or focus me</button> `, +}) +class TooltipHostComponent { + tooltipText = "Hello Tooltip"; +} + +/** Minimal strategy shape the directive expects */ +interface StrategyLike { + withFlexibleDimensions: (flex: boolean) => StrategyLike; + withPush: (push: boolean) => StrategyLike; + withPositions: (positions: ReadonlyArray<ConnectionPositionPair>) => StrategyLike; + readonly positionChanges: Observable<ConnectedOverlayPositionChange>; +} + +/** Minimal Overlay service shape */ +interface OverlayLike { + position: () => { flexibleConnectedTo: (_: unknown) => StrategyLike }; + create: (_: OverlayConfig) => OverlayRefStub; + scrollStrategies: { reposition: () => unknown }; +} + +interface OverlayRefStub { + attach: (portal: ComponentPortal<unknown>) => unknown; + updatePosition: () => void; +} + +describe("TooltipDirective (visibility only)", () => { + let fixture: ComponentFixture<TooltipHostComponent>; + + beforeEach(() => { + const positionChanges$ = new Subject<ConnectedOverlayPositionChange>(); + + const strategy: StrategyLike = { + withFlexibleDimensions: jest.fn(() => strategy), + withPush: jest.fn(() => strategy), + withPositions: jest.fn(() => strategy), + get positionChanges() { + return positionChanges$.asObservable(); + }, + }; + + const overlayRefStub: OverlayRefStub = { + attach: jest.fn(() => ({})), + updatePosition: jest.fn(), + }; + + const overlayMock: OverlayLike = { + position: () => ({ flexibleConnectedTo: () => strategy }), + create: (_: OverlayConfig) => overlayRefStub, + scrollStrategies: { reposition: () => ({}) }, + }; + + TestBed.configureTestingModule({ + imports: [TooltipHostComponent], + providers: [{ provide: Overlay, useValue: overlayMock as unknown as Overlay }], + }); + + fixture = TestBed.createComponent(TooltipHostComponent); + fixture.detectChanges(); + }); + + function getDirective(): TooltipDirective { + const hostDE = fixture.debugElement.query(By.directive(TooltipDirective)); + return hostDE.injector.get(TooltipDirective); + } + + it("sets isVisible to true on mouseenter", () => { + const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement; + const directive = getDirective(); + + const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible; + + button.dispatchEvent(new Event("mouseenter")); + expect(isVisible()).toBe(true); + }); + + it("sets isVisible to true on focus", () => { + const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement; + const directive = getDirective(); + + const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible; + + button.dispatchEvent(new Event("focus")); + expect(isVisible()).toBe(true); + }); +}); diff --git a/libs/components/src/tooltip/tooltip.stories.ts b/libs/components/src/tooltip/tooltip.stories.ts new file mode 100644 index 00000000000..8ea3f52f913 --- /dev/null +++ b/libs/components/src/tooltip/tooltip.stories.ts @@ -0,0 +1,153 @@ +import { signal } from "@angular/core"; +import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { getByRole, userEvent } from "@storybook/test"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { ButtonComponent } from "../button"; +import { BitIconButtonComponent } from "../icon-button"; +import { I18nMockService } from "../utils"; + +import { TooltipPosition, TooltipPositionIdentifier, tooltipPositions } from "./tooltip-positions"; +import { TOOLTIP_DATA, TooltipComponent } from "./tooltip.component"; +import { TooltipDirective } from "./tooltip.directive"; + +import { formatArgsForCodeSnippet } from ".storybook/format-args-for-code-snippet"; + +export default { + title: "Component Library/Tooltip", + component: TooltipDirective, + decorators: [ + moduleMetadata({ + imports: [TooltipDirective, TooltipComponent, BitIconButtonComponent, ButtonComponent], + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + loading: "Loading", + }); + }, + }, + { + provide: TOOLTIP_DATA, + useFactory: () => { + // simple fixed demo values for the Default story + return { + content: signal("This is a tooltip"), + isVisible: signal(true), + tooltipPosition: signal<TooltipPositionIdentifier>("above-center"), + }; + }, + }, + ], + }), + ], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?m=auto&node-id=30558-13730&t=4k23PtzCwqDekAZW-1", + }, + }, + argTypes: { + bitTooltip: { + control: "text", + description: "Text content of the tooltip", + }, + tooltipPosition: { + control: "select", + options: tooltipPositions.map((position: TooltipPosition) => position.id), + description: "Position of the tooltip relative to the element", + table: { + type: { + summary: tooltipPositions.map((position: TooltipPosition) => position.id).join(" | "), + }, + defaultValue: { summary: "above-center" }, + }, + }, + }, +} as Meta<TooltipDirective>; + +type Story = StoryObj<TooltipDirective>; + +export const Default: Story = { + args: { + bitTooltip: "This is a tooltip", + tooltipPosition: "above-center", + }, + render: (args) => ({ + props: args, + template: ` + <div class="tw-p-4"> + <button + bitIconButton="bwi-ellipsis-v" + ${formatArgsForCodeSnippet<TooltipDirective>(args)} + > + Button label here + </button> + </div> + `, + }), + play: async (context) => { + const canvasEl = context.canvasElement; + const button = getByRole(canvasEl, "button"); + + await userEvent.hover(button); + }, +}; + +export const AllPositions: Story = { + render: () => ({ + template: ` + <div class="tw-p-16 tw-grid tw-grid-cols-2 tw-gap-8 tw-place-items-center"> + <button + bitIconButton="bwi-angle-up" + bitTooltip="Top tooltip" + tooltipPosition="above-center" + ></button> + <button + bitIconButton="bwi-angle-right" + bitTooltip="Right tooltip" + tooltipPosition="right-center" + ></button> + <button + bitIconButton="bwi-angle-left" + bitTooltip="Left tooltip" + tooltipPosition="left-center" + ></button> + <button + bitIconButton="bwi-angle-down" + bitTooltip="Bottom tooltip" + tooltipPosition="below-center" + ></button> + </div> + `, + }), +}; + +export const LongContent: Story = { + render: () => ({ + template: ` + <div class="tw-p-16 tw-flex tw-items-center tw-justify-center"> + <button + bitIconButton="bwi-ellipsis-v" + bitTooltip="This is a very long tooltip that will wrap to multiple lines to demonstrate how the tooltip handles long content. This is not recommended for usability." + ></button> + </div> + `, + }), +}; + +export const OnDisabledButton: Story = { + render: () => ({ + template: ` + <div class="tw-p-16 tw-flex tw-items-center tw-justify-center"> + <button + bitIconButton="bwi-ellipsis-v" + bitTooltip="Tooltip on disabled button" + [disabled]="true" + ></button> + </div> + `, + }), +}; diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css index ec29bc522eb..1e0a6f438f0 100644 --- a/libs/components/src/tw-theme.css +++ b/libs/components/src/tw-theme.css @@ -5,6 +5,7 @@ @import "./popover/popover.component.css"; @import "./toast/toast.tokens.css"; @import "./toast/toastr.css"; +@import "./tooltip/tooltip.component.css"; @import "./search/search.component.css"; @tailwind base; From e5ffd7f09ff39f55e077dbe5fda11007eb2328cc Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:22:23 -0500 Subject: [PATCH 22/83] Ignore .serena (#16688) --- .gitignore | 1 + .serena/.gitignore | 1 - .serena/project.yml | 67 --------------------------------------------- 3 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 .serena/.gitignore delete mode 100644 .serena/project.yml diff --git a/.gitignore b/.gitignore index 0f609335b63..61a20195592 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ Thumbs.db .settings/ *.sublime-workspace .claude +.serena # Visual Studio Code .vscode/* diff --git a/.serena/.gitignore b/.serena/.gitignore deleted file mode 100644 index 14d86ad6230..00000000000 --- a/.serena/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/cache diff --git a/.serena/project.yml b/.serena/project.yml deleted file mode 100644 index 1886c87239a..00000000000 --- a/.serena/project.yml +++ /dev/null @@ -1,67 +0,0 @@ -# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby) -# * For C, use cpp -# * For JavaScript, use typescript -# Special requirements: -# * csharp: Requires the presence of a .sln file in the project folder. -language: typescript - -# whether to use the project's gitignore file to ignore files -# Added on 2025-04-07 -ignore_all_files_in_gitignore: true -# list of additional paths to ignore -# same syntax as gitignore, so you can use * and ** -# Was previously called `ignored_dirs`, please update your config if you are using that. -# Added (renamed) on 2025-04-07 -ignored_paths: [] - -# whether the project is in read-only mode -# If set to true, all editing tools will be disabled and attempts to use them will result in an error -# Added on 2025-04-18 -read_only: false - -# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. -excluded_tools: [] - -# initial prompt for the project. It will always be given to the LLM upon activating the project -# (contrary to the memories, which are loaded on demand). -initial_prompt: "" - -project_name: "clients" From 8cb908ef6879b8d0bbe675ac6dcd8311b92e30d5 Mon Sep 17 00:00:00 2001 From: Colton Hurst <colton@coltonhurst.com> Date: Wed, 1 Oct 2025 15:03:52 -0400 Subject: [PATCH 23/83] [PM-26319] Fix default ctrl-shift-b issue (#16683) --- .../desktop/src/autofill/services/desktop-autotype.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/autofill/services/desktop-autotype.service.ts b/apps/desktop/src/autofill/services/desktop-autotype.service.ts index 6f9289f3513..24ec3907a62 100644 --- a/apps/desktop/src/autofill/services/desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autotype.service.ts @@ -73,7 +73,9 @@ export class DesktopAutotypeService { async init() { this.autotypeEnabledUserSetting$ = this.autotypeEnabledState.state$; - this.autotypeKeyboardShortcut$ = this.autotypeKeyboardShortcut.state$; + this.autotypeKeyboardShortcut$ = this.autotypeKeyboardShortcut.state$.pipe( + map((shortcut) => shortcut ?? defaultWindowsAutotypeKeyboardShortcut), + ); // Currently Autotype is only supported for Windows if (this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop) { From 420b26776bf14521e5c71e20751640ebeb63f3fe Mon Sep 17 00:00:00 2001 From: Konrad <11725227+mKoonrad@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:36:02 +0200 Subject: [PATCH 24/83] [PM-26325] Archive string - separate noun and verb (#16652) * Separation of noun and verb --- apps/browser/src/_locales/en/messages.json | 9 +++++++-- .../item-more-options/item-more-options.component.html | 2 +- .../popup/settings/vault-settings-v2.component.html | 2 +- apps/desktop/src/locales/en/messages.json | 9 +++++++-- .../vault-filter/components/vault-filter.component.ts | 2 +- .../vault-header/vault-header.component.ts | 2 +- apps/web/src/locales/en/messages.json | 9 +++++++-- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 72c3892af62..4e399a530e1 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html index 16cc04ce612..7ee7e141ee5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html @@ -40,7 +40,7 @@ </ng-container> @if (canArchive$ | async) { <button type="button" bitMenuItem (click)="archive()" *ngIf="canArchive$ | async"> - {{ "archive" | i18n }} + {{ "archiveVerb" | i18n }} </button> } </bit-menu> diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html index 630c55d0038..3c1278b4d44 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html @@ -37,7 +37,7 @@ @if (userCanArchive() || showArchiveFilter()) { <bit-item> <a bit-item-content routerLink="/archive"> - {{ "archive" | i18n }} + {{ "archiveNoun" | i18n }} <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> </a> </bit-item> diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 08ec76af874..a9b5efda357 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -4114,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts index 0a8c0f6f1d0..278ae62aaa6 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts @@ -424,7 +424,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { [ { id: "archive", - name: this.i18nService.t("archive"), + name: this.i18nService.t("archiveNoun"), type: "archive", icon: "bwi-archive", }, diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 63e6d391b42..aaa3a90aa98 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -140,7 +140,7 @@ export class VaultHeaderComponent { } if (this.filter.type === "archive") { - return this.i18nService.t("archive"); + return this.i18nService.t("archiveNoun"); } const activeOrganization = this.activeOrganization; diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 35f369aa647..e107b34d038 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -11078,8 +11078,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" From cae58232e5884b272fb26b1baa5aa66852528b9d Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:57:41 -0700 Subject: [PATCH 25/83] feat(new-device-verification-screen): (Auth) [PM-17489] Back Button on New Device Verification Screen (#16599) On Web and Desktop, show back button on `NewDeviceVerificationComponent` (route `/device-verification`). Do not show it on Extension, because Extension already has a back button in the header. --- ...ice-verification-component.service.spec.ts | 21 +++++++++++++++++++ ...w-device-verification-component.service.ts | 13 ++++++++++++ .../src/popup/services/services.module.ts | 7 +++++++ .../src/services/jslib-services.module.ts | 7 +++++++ libs/auth/src/angular/index.ts | 2 ++ ...ice-verification-component.service.spec.ts | 21 +++++++++++++++++++ ...w-device-verification-component.service.ts | 9 ++++++++ ...w-device-verification-component.service.ts | 8 +++++++ .../new-device-verification.component.html | 10 ++++++++- .../new-device-verification.component.ts | 15 ++++++++++--- 10 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.spec.ts create mode 100644 apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.ts create mode 100644 libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.spec.ts create mode 100644 libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.ts create mode 100644 libs/auth/src/angular/new-device-verification/new-device-verification-component.service.ts diff --git a/apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.spec.ts b/apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.spec.ts new file mode 100644 index 00000000000..7c91cae3fcb --- /dev/null +++ b/apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.spec.ts @@ -0,0 +1,21 @@ +import { ExtensionNewDeviceVerificationComponentService } from "./extension-new-device-verification-component.service"; + +describe("ExtensionNewDeviceVerificationComponentService", () => { + let sut: ExtensionNewDeviceVerificationComponentService; + + beforeEach(() => { + sut = new ExtensionNewDeviceVerificationComponentService(); + }); + + it("should instantiate the service", () => { + expect(sut).not.toBeFalsy(); + }); + + describe("showBackButton()", () => { + it("should return false", () => { + const result = sut.showBackButton(); + + expect(result).toBe(false); + }); + }); +}); diff --git a/apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.ts b/apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.ts new file mode 100644 index 00000000000..05e60fc8dad --- /dev/null +++ b/apps/browser/src/auth/services/new-device-verification/extension-new-device-verification-component.service.ts @@ -0,0 +1,13 @@ +import { + DefaultNewDeviceVerificationComponentService, + NewDeviceVerificationComponentService, +} from "@bitwarden/auth/angular"; + +export class ExtensionNewDeviceVerificationComponentService + extends DefaultNewDeviceVerificationComponentService + implements NewDeviceVerificationComponentService +{ + showBackButton() { + return false; + } +} diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index ef4dd0be090..7a10dc2343f 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -29,6 +29,7 @@ import { TwoFactorAuthDuoComponentService, TwoFactorAuthWebAuthnComponentService, SsoComponentService, + NewDeviceVerificationComponentService, } from "@bitwarden/auth/angular"; import { LockService, @@ -36,6 +37,7 @@ import { SsoUrlService, LogoutService, } from "@bitwarden/auth/common"; +import { ExtensionNewDeviceVerificationComponentService } from "@bitwarden/browser/auth/services/new-device-verification/extension-new-device-verification-component.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -710,6 +712,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultCipherArchiveService, deps: [CipherService, ApiService, BillingAccountProfileStateService, ConfigService], }), + safeProvider({ + provide: NewDeviceVerificationComponentService, + useClass: ExtensionNewDeviceVerificationComponentService, + deps: [], + }), ]; @NgModule({ diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 8c727a98d11..3304c54a86f 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -20,11 +20,13 @@ import { import { DefaultLoginComponentService, DefaultLoginDecryptionOptionsService, + DefaultNewDeviceVerificationComponentService, DefaultRegistrationFinishService, DefaultTwoFactorAuthComponentService, DefaultTwoFactorAuthWebAuthnComponentService, LoginComponentService, LoginDecryptionOptionsService, + NewDeviceVerificationComponentService, RegistrationFinishService as RegistrationFinishServiceAbstraction, TwoFactorAuthComponentService, TwoFactorAuthWebAuthnComponentService, @@ -1646,6 +1648,11 @@ const safeProviders: SafeProvider[] = [ ConfigService, ], }), + safeProvider({ + provide: NewDeviceVerificationComponentService, + useClass: DefaultNewDeviceVerificationComponentService, + deps: [], + }), ]; @NgModule({ diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index fdd06509511..454a9091c25 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -59,6 +59,8 @@ export * from "./two-factor-auth"; // device verification export * from "./new-device-verification/new-device-verification.component"; +export * from "./new-device-verification/new-device-verification-component.service"; +export * from "./new-device-verification/default-new-device-verification-component.service"; // validators export * from "./validators/compare-inputs.validator"; diff --git a/libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.spec.ts b/libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.spec.ts new file mode 100644 index 00000000000..a2ea26268ea --- /dev/null +++ b/libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.spec.ts @@ -0,0 +1,21 @@ +import { DefaultNewDeviceVerificationComponentService } from "./default-new-device-verification-component.service"; + +describe("DefaultNewDeviceVerificationComponentService", () => { + let sut: DefaultNewDeviceVerificationComponentService; + + beforeEach(() => { + sut = new DefaultNewDeviceVerificationComponentService(); + }); + + it("should instantiate the service", () => { + expect(sut).not.toBeFalsy(); + }); + + describe("showBackButton()", () => { + it("should return true", () => { + const result = sut.showBackButton(); + + expect(result).toBe(true); + }); + }); +}); diff --git a/libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.ts b/libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.ts new file mode 100644 index 00000000000..88ea652bc4b --- /dev/null +++ b/libs/auth/src/angular/new-device-verification/default-new-device-verification-component.service.ts @@ -0,0 +1,9 @@ +import { NewDeviceVerificationComponentService } from "./new-device-verification-component.service"; + +export class DefaultNewDeviceVerificationComponentService + implements NewDeviceVerificationComponentService +{ + showBackButton() { + return true; + } +} diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification-component.service.ts b/libs/auth/src/angular/new-device-verification/new-device-verification-component.service.ts new file mode 100644 index 00000000000..c34fc40ef00 --- /dev/null +++ b/libs/auth/src/angular/new-device-verification/new-device-verification-component.service.ts @@ -0,0 +1,8 @@ +export abstract class NewDeviceVerificationComponentService { + /** + * States whether component should show a back button. Can be overridden by client-specific component services. + * - Default = `true` + * - Extension = `false` (because Extension shows a back button in the header instead) + */ + abstract showBackButton: () => boolean; +} diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.html b/libs/auth/src/angular/new-device-verification/new-device-verification.component.html index e731f3afcb6..814c48db0fc 100644 --- a/libs/auth/src/angular/new-device-verification/new-device-verification.component.html +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.html @@ -22,7 +22,7 @@ {{ "resendCode" | i18n }} </button> - <div class="tw-flex tw-mt-4"> + <div class="tw-grid tw-gap-3 tw-mt-4"> <button bitButton bitFormButton @@ -33,5 +33,13 @@ > {{ "continueLoggingIn" | i18n }} </button> + + @if (showBackButton) { + <div class="tw-text-center">{{ "or" | i18n }}</div> + + <button type="button" bitButton block buttonType="secondary" (click)="goBack()"> + {{ "back" | i18n }} + </button> + } </div> </form> diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts index 6362b901fc8..2211b3390a7 100644 --- a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts @@ -1,4 +1,4 @@ -import { CommonModule } from "@angular/common"; +import { CommonModule, Location } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { Router } from "@angular/router"; @@ -11,7 +11,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. @@ -26,6 +25,8 @@ import { import { LoginStrategyServiceAbstraction } from "../../common/abstractions/login-strategy.service"; +import { NewDeviceVerificationComponentService } from "./new-device-verification-component.service"; + /** * Component for verifying a new device via a one-time password (OTP). */ @@ -57,6 +58,7 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy { protected disableRequestOTP = false; private destroy$ = new Subject<void>(); protected authenticationSessionTimeoutRoute = "/authentication-timeout"; + protected showBackButton = true; constructor( private router: Router, @@ -66,12 +68,15 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy { private logService: LogService, private i18nService: I18nService, private loginSuccessHandlerService: LoginSuccessHandlerService, - private configService: ConfigService, private accountService: AccountService, private masterPasswordService: MasterPasswordServiceAbstraction, + private newDeviceVerificationComponentService: NewDeviceVerificationComponentService, + private location: Location, ) {} async ngOnInit() { + this.showBackButton = this.newDeviceVerificationComponentService.showBackButton(); + // Redirect to timeout route if session expires this.loginStrategyService.authenticationSessionTimeout$ .pipe(takeUntil(this.destroy$)) @@ -179,4 +184,8 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy { codeControl.markAsTouched(); } }; + + protected goBack() { + this.location.back(); + } } From 5de8a145ecaf6574252001318e7d4c69eb90a1ef Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Wed, 1 Oct 2025 16:20:03 -0400 Subject: [PATCH 26/83] [PM-26410] Update autotype policy to include all org members (#16689) * PM-26410 use policies$ to apply default behavior to all org members * linting error, remove unused imports --- .../desktop-autotype-policy.service.spec.ts | 177 +++++++++++++++--- .../desktop-autotype-policy.service.ts | 19 +- 2 files changed, 164 insertions(+), 32 deletions(-) diff --git a/apps/desktop/src/autofill/services/desktop-autotype-policy.service.spec.ts b/apps/desktop/src/autofill/services/desktop-autotype-policy.service.spec.ts index 7fb30333e28..555e6ceef5b 100644 --- a/apps/desktop/src/autofill/services/desktop-autotype-policy.service.spec.ts +++ b/apps/desktop/src/autofill/services/desktop-autotype-policy.service.spec.ts @@ -1,8 +1,10 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, firstValueFrom, take, timeout, TimeoutError } from "rxjs"; +import { BehaviorSubject, firstValueFrom, take } from "rxjs"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -18,10 +20,10 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { let policyService: MockProxy<InternalPolicyService>; let configService: MockProxy<ConfigService>; - let mockAccountSubject: BehaviorSubject<{ id: UserId } | null>; + let mockAccountSubject: BehaviorSubject<Account | null>; let mockFeatureFlagSubject: BehaviorSubject<boolean>; let mockAuthStatusSubject: BehaviorSubject<AuthenticationStatus>; - let mockPolicyAppliesSubject: BehaviorSubject<boolean>; + let mockPoliciesSubject: BehaviorSubject<Policy[]>; const mockUserId = "user-123" as UserId; @@ -36,7 +38,7 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { mockAuthStatusSubject = new BehaviorSubject<AuthenticationStatus>( AuthenticationStatus.Unlocked, ); - mockPolicyAppliesSubject = new BehaviorSubject<boolean>(false); + mockPoliciesSubject = new BehaviorSubject<Policy[]>([]); accountService = mock<AccountService>(); authService = mock<AuthService>(); @@ -50,9 +52,7 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { authService.authStatusFor$ = jest .fn() .mockImplementation((_: UserId) => mockAuthStatusSubject.asObservable()); - policyService.policyAppliesToUser$ = jest - .fn() - .mockReturnValue(mockPolicyAppliesSubject.asObservable()); + policyService.policies$ = jest.fn().mockReturnValue(mockPoliciesSubject.asObservable()); TestBed.configureTestingModule({ providers: [ @@ -72,7 +72,7 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { mockAccountSubject.complete(); mockFeatureFlagSubject.complete(); mockAuthStatusSubject.complete(); - mockPolicyAppliesSubject.complete(); + mockPoliciesSubject.complete(); }); describe("autotypeDefaultSetting$", () => { @@ -82,11 +82,20 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { expect(result).toBeNull(); }); - it("should not emit when no active account", async () => { + it("does not emit until an account appears", async () => { mockAccountSubject.next(null); - await expect( - firstValueFrom(service.autotypeDefaultSetting$.pipe(timeout({ first: 30 }))), - ).rejects.toBeInstanceOf(TimeoutError); + + mockAccountSubject.next({ id: mockUserId } as Account); + mockAuthStatusSubject.next(AuthenticationStatus.Unlocked); + mockPoliciesSubject.next([ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: true, + } as Policy, + ]); + + const result = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); + expect(result).toBe(true); }); it("should emit null when user is not unlocked", async () => { @@ -96,34 +105,56 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { }); it("should emit null when no autotype policy exists", async () => { - mockPolicyAppliesSubject.next(false); + mockPoliciesSubject.next([]); const policy = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); expect(policy).toBeNull(); }); it("should emit true when autotype policy is enabled", async () => { - mockPolicyAppliesSubject.next(true); + mockPoliciesSubject.next([ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: true, + } as Policy, + ]); const policyStatus = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); expect(policyStatus).toBe(true); }); - it("should emit false when autotype policy is disabled", async () => { - mockPolicyAppliesSubject.next(false); + it("should emit null when autotype policy is disabled", async () => { + mockPoliciesSubject.next([ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: false, + } as Policy, + ]); const policyStatus = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); expect(policyStatus).toBeNull(); }); it("should emit null when autotype policy does not apply", async () => { - mockPolicyAppliesSubject.next(false); + mockPoliciesSubject.next([ + { + type: PolicyType.RequireSso, + enabled: true, + } as Policy, + ]); const policy = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); expect(policy).toBeNull(); }); it("should react to authentication status changes", async () => { + mockPoliciesSubject.next([ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: true, + } as Policy, + ]); + // Expect one emission when unlocked mockAuthStatusSubject.next(AuthenticationStatus.Unlocked); const first = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); - expect(first).toBeNull(); + expect(first).toBe(true); // Expect null emission when locked mockAuthStatusSubject.next(AuthenticationStatus.Locked); @@ -134,33 +165,131 @@ describe("DesktopAutotypeDefaultSettingPolicy", () => { it("should react to account changes", async () => { const newUserId = "user-456" as UserId; + mockPoliciesSubject.next([ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: true, + } as Policy, + ]); + // First value for original user const firstValue = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); - expect(firstValue).toBeNull(); + expect(firstValue).toBe(true); // Change account and expect a new emission mockAccountSubject.next({ id: newUserId, - }); + } as Account); const secondValue = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); - expect(secondValue).toBeNull(); + expect(secondValue).toBe(true); // Verify the auth lookup was switched to the new user expect(authService.authStatusFor$).toHaveBeenCalledWith(newUserId); + expect(policyService.policies$).toHaveBeenCalledWith(newUserId); }); it("should react to policy changes", async () => { - mockPolicyAppliesSubject.next(false); + mockPoliciesSubject.next([]); const nullValue = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); expect(nullValue).toBeNull(); - mockPolicyAppliesSubject.next(true); + mockPoliciesSubject.next([ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: true, + } as Policy, + ]); const trueValue = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); expect(trueValue).toBe(true); - mockPolicyAppliesSubject.next(false); + mockPoliciesSubject.next([]); const nullValueAgain = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); expect(nullValueAgain).toBeNull(); }); + + it("emits null again if the feature flag turns off after emitting", async () => { + mockPoliciesSubject.next([ + { type: PolicyType.AutotypeDefaultSetting, enabled: true } as Policy, + ]); + expect(await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1)))).toBe(true); + + mockFeatureFlagSubject.next(false); + expect(await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1)))).toBeNull(); + }); + + it("replays the latest value to late subscribers", async () => { + mockPoliciesSubject.next([ + { type: PolicyType.AutotypeDefaultSetting, enabled: true } as Policy, + ]); + + await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); + + const late = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); + expect(late).toBe(true); + }); + + it("does not re-emit when effective value is unchanged", async () => { + mockAccountSubject.next({ id: mockUserId } as Account); + mockAuthStatusSubject.next(AuthenticationStatus.Unlocked); + + const policies = [ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: true, + } as Policy, + ]; + + mockPoliciesSubject.next(policies); + const first = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); + expect(first).toBe(true); + + let emissionCount = 0; + const subscription = service.autotypeDefaultSetting$.subscribe(() => { + emissionCount++; + }); + + mockPoliciesSubject.next(policies); + + await new Promise((resolve) => setTimeout(resolve, 50)); + subscription.unsubscribe(); + + expect(emissionCount).toBe(1); + }); + + it("does not emit policy values while locked; emits after unlocking", async () => { + mockAuthStatusSubject.next(AuthenticationStatus.Locked); + mockPoliciesSubject.next([ + { type: PolicyType.AutotypeDefaultSetting, enabled: true } as Policy, + ]); + + expect(await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1)))).toBeNull(); + + mockAuthStatusSubject.next(AuthenticationStatus.Unlocked); + expect(await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1)))).toBe(true); + }); + + it("emits correctly if auth unlocks before policies arrive", async () => { + mockAccountSubject.next({ id: mockUserId } as Account); + mockAuthStatusSubject.next(AuthenticationStatus.Unlocked); + mockPoliciesSubject.next([ + { + type: PolicyType.AutotypeDefaultSetting, + enabled: true, + } as Policy, + ]); + + const result = await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); + expect(result).toBe(true); + }); + + it("wires dependencies with initial user id", async () => { + mockPoliciesSubject.next([ + { type: PolicyType.AutotypeDefaultSetting, enabled: true } as Policy, + ]); + await firstValueFrom(service.autotypeDefaultSetting$.pipe(take(1))); + + expect(authService.authStatusFor$).toHaveBeenCalledWith(mockUserId); + expect(policyService.policies$).toHaveBeenCalledWith(mockUserId); + }); }); }); diff --git a/apps/desktop/src/autofill/services/desktop-autotype-policy.service.ts b/apps/desktop/src/autofill/services/desktop-autotype-policy.service.ts index 887a30ef6f6..d3ae67d2c8d 100644 --- a/apps/desktop/src/autofill/services/desktop-autotype-policy.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autotype-policy.service.ts @@ -34,7 +34,7 @@ export class DesktopAutotypeDefaultSettingPolicy { } return this.accountService.activeAccount$.pipe( - filter((account) => account != null), + filter((account) => account != null && account.id != null), getUserId, distinctUntilChanged(), switchMap((userId) => { @@ -43,13 +43,16 @@ export class DesktopAutotypeDefaultSettingPolicy { distinctUntilChanged(), ); - const policy$ = this.policyService - .policyAppliesToUser$(PolicyType.AutotypeDefaultSetting, userId) - .pipe( - map((appliesToUser) => (appliesToUser ? true : null)), - distinctUntilChanged(), - shareReplay({ bufferSize: 1, refCount: true }), - ); + const policy$ = this.policyService.policies$(userId).pipe( + map((policies) => { + const autotypePolicy = policies.find( + (policy) => policy.type === PolicyType.AutotypeDefaultSetting && policy.enabled, + ); + return autotypePolicy ? true : null; + }), + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }), + ); return isUnlocked$.pipe(switchMap((unlocked) => (unlocked ? policy$ : of(null)))); }), From 688647b2c69243b14ec14a37b51639da400a6bf2 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:21:11 -0700 Subject: [PATCH 27/83] [PM-25216] - handle extension already installed on new user creation (#16650) * handle extension already installed on new user creation * fix tests * remove comment --- .../setup-extension.component.html | 25 ++++++++++++++++--- .../setup-extension.component.spec.ts | 7 +----- .../setup-extension.component.ts | 6 +++-- .../setup-extension-redirect.guard.spec.ts | 7 ------ .../guards/setup-extension-redirect.guard.ts | 14 ----------- apps/web/src/locales/en/messages.json | 6 +++++ 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html index 41ee1b4707e..09bd38c8517 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html @@ -29,15 +29,32 @@ </div> </section> -<section *ngIf="state === SetupExtensionState.Success" class="tw-flex tw-flex-col tw-items-center"> +<section + *ngIf="state === SetupExtensionState.Success || state === SetupExtensionState.AlreadyInstalled" + class="tw-flex tw-flex-col tw-items-center" +> <div class="tw-size-[90px]"> <bit-icon [icon]="PartyIcon"></bit-icon> </div> - <h1 bitTypography="h2" class="tw-mb-6 tw-mt-4">{{ "bitwardenExtensionInstalled" | i18n }}</h1> + <h1 bitTypography="h2" class="tw-mb-6 tw-mt-4 tw-text-center"> + {{ + (state === SetupExtensionState.Success + ? "bitwardenExtensionInstalled" + : "openTheBitwardenExtension" + ) | i18n + }} + </h1> <div - class="tw-flex tw-flex-col tw-rounded-2xl tw-bg-background tw-border tw-border-solid tw-border-secondary-300 tw-p-8" + class="tw-flex tw-flex-col tw-rounded-2xl tw-bg-background tw-border tw-border-solid tw-border-secondary-300 tw-p-8 tw-max-w-md tw-text-center" > - <p>{{ "openExtensionToAutofill" | i18n }}</p> + <p> + {{ + (state === SetupExtensionState.Success + ? "openExtensionToAutofill" + : "bitwardenExtensionInstalledOpenExtension" + ) | i18n + }} + </p> <button type="button" bitButton buttonType="primary" class="tw-mb-2" (click)="openExtension()"> {{ "openBitwardenExtension" | i18n }} </button> diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts index 3be8251b1d7..f755c83832f 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts @@ -93,14 +93,9 @@ describe("SetupExtensionComponent", () => { }); describe("extensionInstalled$", () => { - it("redirects the user to the vault when the first emitted value is true", () => { - extensionInstalled$.next(true); - - expect(navigate).toHaveBeenCalledWith(["/vault"]); - }); - describe("success state", () => { beforeEach(() => { + update.mockClear(); // avoid initial redirect extensionInstalled$.next(false); diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts index 558f0eb06c9..a04c529004c 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts @@ -36,6 +36,7 @@ export const SetupExtensionState = { Loading: "loading", NeedsExtension: "needs-extension", Success: "success", + AlreadyInstalled: "already-installed", ManualOpen: "manual-open", } as const; @@ -99,9 +100,10 @@ export class SetupExtensionComponent implements OnInit, OnDestroy { this.webBrowserExtensionInteractionService.extensionInstalled$ .pipe(takeUntilDestroyed(this.destroyRef), startWith(null), pairwise()) .subscribe(([previousState, currentState]) => { - // Initial state transitioned to extension installed, redirect the user + // User landed on the page and the extension is already installed, show already installed state if (previousState === null && currentState) { - void this.router.navigate(["/vault"]); + void this.dismissExtensionPage(); + this.state = SetupExtensionState.AlreadyInstalled; } // Extension was not installed and now it is, show success state diff --git a/apps/web/src/app/vault/guards/setup-extension-redirect.guard.spec.ts b/apps/web/src/app/vault/guards/setup-extension-redirect.guard.spec.ts index 14f6763dbf9..7520451df34 100644 --- a/apps/web/src/app/vault/guards/setup-extension-redirect.guard.spec.ts +++ b/apps/web/src/app/vault/guards/setup-extension-redirect.guard.spec.ts @@ -82,13 +82,6 @@ describe("setupExtensionRedirectGuard", () => { expect(await setupExtensionGuard()).toBe(true); }); - it("returns `true` when the user has the extension installed", async () => { - state$.next(false); - extensionInstalled$.next(true); - - expect(await setupExtensionGuard()).toBe(true); - }); - it('redirects the user to "/setup-extension" when all criteria do not pass', async () => { state$.next(false); extensionInstalled$.next(false); diff --git a/apps/web/src/app/vault/guards/setup-extension-redirect.guard.ts b/apps/web/src/app/vault/guards/setup-extension-redirect.guard.ts index 5355ebf7232..0c9e31ae77f 100644 --- a/apps/web/src/app/vault/guards/setup-extension-redirect.guard.ts +++ b/apps/web/src/app/vault/guards/setup-extension-redirect.guard.ts @@ -11,8 +11,6 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; -import { WebBrowserInteractionService } from "../services/web-browser-interaction.service"; - export const SETUP_EXTENSION_DISMISSED = new UserKeyDefinition<boolean>( SETUP_EXTENSION_DISMISSED_DISK, "setupExtensionDismissed", @@ -27,7 +25,6 @@ export const setupExtensionRedirectGuard: CanActivateFn = async () => { const accountService = inject(AccountService); const vaultProfileService = inject(VaultProfileService); const stateProvider = inject(StateProvider); - const webBrowserInteractionService = inject(WebBrowserInteractionService); const isMobile = Utils.isMobileBrowser; @@ -43,10 +40,6 @@ export const setupExtensionRedirectGuard: CanActivateFn = async () => { return router.createUrlTree(["/login"]); } - const hasExtensionInstalledPromise = firstValueFrom( - webBrowserInteractionService.extensionInstalled$, - ); - const dismissedExtensionPage = await firstValueFrom( stateProvider .getUser(currentAcct.id, SETUP_EXTENSION_DISMISSED) @@ -66,13 +59,6 @@ export const setupExtensionRedirectGuard: CanActivateFn = async () => { return true; } - // Checking for the extension is a more expensive operation, do it last to avoid unnecessary delays. - const hasExtensionInstalled = await hasExtensionInstalledPromise; - - if (hasExtensionInstalled) { - return true; - } - return router.createUrlTree(["/setup-extension"]); }; diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index e107b34d038..50b361f3d5a 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -11199,6 +11199,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, From 75253c77095a2912c8bd7ef7ba27e58e259217ea Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:26:30 -0700 Subject: [PATCH 28/83] [PM-24099] Tools - Remove getOrgKey from the key service (#16351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * replace deprecated getOrgKey() method • obtain account using `accountService.activeAccount$` and then use id property to guarentee validity of UserId --- .../bitwarden/bitwarden-json-importer.ts | 14 +++-- ...warden-password-protected-importer.spec.ts | 53 +++++++++++++++++++ .../src/services/org-vault-export.service.ts | 22 +++++--- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 70c783df52a..14a16211deb 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -64,12 +64,13 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseEncrypted( results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport, ) { + const account = await firstValueFrom(this.accountService.activeAccount$); + if (results.encKeyValidation_DO_NOT_EDIT != null) { - let keyForDecryption: SymmetricCryptoKey = await this.keyService.getOrgKey( - this.organizationId, - ); + const orgKeys = await firstValueFrom(this.keyService.orgKeys$(account.id)); + let keyForDecryption: SymmetricCryptoKey = orgKeys?.[this.organizationId]; if (keyForDecryption == null) { - keyForDecryption = await this.keyService.getUserKey(); + keyForDecryption = await firstValueFrom(this.keyService.userKey$(account.id)); } const encKeyValidation = new EncString(results.encKeyValidation_DO_NOT_EDIT); try { @@ -113,10 +114,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { }); } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const view = await this.cipherService.decrypt(cipher, activeUserId); + const view = await this.cipherService.decrypt(cipher, account.id); this.cleanupCipher(view); this.result.ciphers.push(view); } diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts index 7812cce2c05..dfdcef51735 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts @@ -1,12 +1,17 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { emptyGuid, OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { newGuid } from "@bitwarden/guid"; import { KdfType, KeyService } from "@bitwarden/key-management"; +import { UserId } from "@bitwarden/user-core"; import { emptyAccountEncrypted } from "../spec-data/bitwarden-json/account-encrypted.json"; import { emptyUnencryptedExport } from "../spec-data/bitwarden-json/unencrypted.json"; @@ -35,6 +40,36 @@ describe("BitwardenPasswordProtectedImporter", () => { pinService = mock<PinServiceAbstraction>(); accountService = mock<AccountService>(); + accountService.activeAccount$ = of({ + id: newGuid() as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + + const mockOrgId = emptyGuid as OrganizationId; + /* + The key values below are never read, empty objects are cast as types for compilation type checking only. + Tests specific to key contents are in key-service.spec.ts + */ + const mockOrgKey = {} as unknown as OrgKey; + const mockUserKey = {} as unknown as UserKey; + + keyService.orgKeys$.mockImplementation(() => + of({ [mockOrgId]: mockOrgKey } as Record<OrganizationId, OrgKey>), + ); + keyService.userKey$.mockImplementation(() => of(mockUserKey)); + (keyService as any).activeUserOrgKeys$ = of({ + [mockOrgId]: mockOrgKey, + } as Record<OrganizationId, OrgKey>); + + /* + Crypto isn’t under test here; keys are just placeholders. + Decryption methods are stubbed to always return empty CipherView or string allowing OK import flow. + */ + cipherService.decrypt.mockResolvedValue({} as any); + encryptService.decryptString.mockResolvedValue("ok"); + importer = new BitwardenPasswordProtectedImporter( keyService, encryptService, @@ -62,6 +97,24 @@ describe("BitwardenPasswordProtectedImporter", () => { jest.spyOn(BitwardenJsonImporter.prototype, "parse"); }); + beforeEach(() => { + accountService.activeAccount$ = of({ + id: newGuid() as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + importer = new BitwardenPasswordProtectedImporter( + keyService, + encryptService, + i18nService, + cipherService, + pinService, + accountService, + promptForPassword_callback, + ); + }); + it("Should call BitwardenJsonImporter", async () => { expect((await importer.parse(emptyAccountEncrypted)).success).toEqual(true); expect(BitwardenJsonImporter.prototype.parse).toHaveBeenCalledWith(emptyAccountEncrypted); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index 3884dde4b18..53952938aa8 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -15,6 +15,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -22,6 +23,7 @@ import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; +import { newGuid } from "@bitwarden/guid"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { @@ -112,7 +114,7 @@ export class OrganizationVaultExportService type: "text/plain", data: onlyManagedCollections ? await this.getEncryptedManagedExport(userId, organizationId) - : await this.getOrganizationEncryptedExport(organizationId), + : await this.getOrganizationEncryptedExport(userId, organizationId), fileName: ExportHelper.getFileName("org", "encrypted_json"), } as ExportedVaultAsString; } @@ -184,7 +186,10 @@ export class OrganizationVaultExportService return this.buildJsonExport(decCollections, decCiphers); } - private async getOrganizationEncryptedExport(organizationId: OrganizationId): Promise<string> { + private async getOrganizationEncryptedExport( + userId: UserId, + organizationId: OrganizationId, + ): Promise<string> { const collections: Collection[] = []; const ciphers: Cipher[] = []; @@ -215,7 +220,7 @@ export class OrganizationVaultExportService } }); } - return this.BuildEncryptedExport(organizationId, collections, ciphers); + return this.BuildEncryptedExport(userId, organizationId, collections, ciphers); } private async getDecryptedManagedExport( @@ -295,16 +300,21 @@ export class OrganizationVaultExportService !this.restrictedItemTypesService.isCipherRestricted(f, restrictions), ); - return this.BuildEncryptedExport(organizationId, encCollections, encCiphers); + return this.BuildEncryptedExport(activeUserId, organizationId, encCollections, encCiphers); } private async BuildEncryptedExport( + activeUserId: UserId, organizationId: OrganizationId, collections: Collection[], ciphers: Cipher[], ): Promise<string> { - const orgKey = await this.keyService.getOrgKey(organizationId); - const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), orgKey); + const orgKeys = await firstValueFrom(this.keyService.orgKeys$(activeUserId)); + const keyForEncryption: SymmetricCryptoKey = orgKeys?.[organizationId]; + if (keyForEncryption == null) { + throw new Error("No encryption key found for organization"); + } + const encKeyValidation = await this.encryptService.encryptString(newGuid(), keyForEncryption); const jsonDoc: BitwardenEncryptedOrgJsonExport = { encrypted: true, From 087e1a615517ca368e39b0fda5f79e85d058033c Mon Sep 17 00:00:00 2001 From: Danielle Flinn <43477473+danielleflinn@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:00:47 -0700 Subject: [PATCH 29/83] [CL-866] Add default callout and update styles (#16481) * Updated callout styles * Added default callout variant * Refactored component to support icon + content variants (with no header) --------- Co-authored-by: Vicki League <vleague@bitwarden.com> Co-authored-by: Bryan Cunningham <bryan.cunningham@me.com> --- .../at-risk-password-callout.component.html | 1 - .../src/callout/callout.component.html | 38 +++++++++-------- .../src/callout/callout.component.spec.ts | 7 ++++ .../src/callout/callout.component.ts | 13 +++--- libs/components/src/callout/callout.mdx | 12 +++++- .../components/src/callout/callout.stories.ts | 41 ++++++++++++++++++- .../cipher-view/cipher-view.component.html | 1 - 7 files changed, 86 insertions(+), 27 deletions(-) diff --git a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.html b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.html index 16d9b6a322a..6c2bc3f77a0 100644 --- a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.html +++ b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.html @@ -1,5 +1,4 @@ <bit-callout *ngIf="(pendingTasks$ | async)?.length as taskCount" type="warning" [title]="''"> - <i class="bwi bwi-exclamation-triangle tw-text-warning" aria-hidden="true"></i> <a bitLink [routerLink]="'/at-risk-passwords'"> {{ (taskCount === 1 ? "reviewAndChangeAtRiskPassword" : "reviewAndChangeAtRiskPasswordsPlural") diff --git a/libs/components/src/callout/callout.component.html b/libs/components/src/callout/callout.component.html index b98679766d5..e0fe0a182ea 100644 --- a/libs/components/src/callout/callout.component.html +++ b/libs/components/src/callout/callout.component.html @@ -1,24 +1,26 @@ <aside - class="tw-mb-4 tw-box-border tw-rounded-lg tw-bg-background tw-ps-3 tw-pe-3 tw-py-2 tw-leading-5 tw-text-main" - [ngClass]="calloutClass()" + class="tw-mb-4 tw-box-border tw-border tw-border-solid tw-rounded-lg tw-bg-background tw-ps-4 tw-pe-4 tw-py-3 tw-leading-5 tw-flex tw-gap-2" + [ngClass]="[calloutClass()]" [attr.aria-labelledby]="titleId" > - @if (titleComputed(); as title) { - <header - id="{{ titleId }}" - class="tw-mb-1 tw-mt-0 tw-text-base tw-font-semibold tw-flex tw-gap-2 tw-items-start" - > - @if (iconComputed(); as icon) { - <i - class="bwi !tw-text-main tw-relative tw-top-[3px]" - [ngClass]="[icon]" - aria-hidden="true" - ></i> - } - {{ title }} - </header> + @let title = titleComputed(); + @let icon = iconComputed(); + + @if (icon) { + <i + class="bwi tw-relative" + [ngClass]="[icon, title ? 'tw-top-[3px] tw-self-start' : 'tw-top-[1px]']" + aria-hidden="true" + ></i> } - <div class="tw-ps-6" bitTypography="body2"> - <ng-content></ng-content> + <div class="tw-flex tw-flex-col tw-gap-0.5"> + @if (title) { + <header id="{{ titleId }}" class="tw-text-base tw-font-semibold"> + {{ title }} + </header> + } + <div bitTypography="body2"> + <ng-content></ng-content> + </div> </div> </aside> diff --git a/libs/components/src/callout/callout.component.spec.ts b/libs/components/src/callout/callout.component.spec.ts index b7dfe29a643..e052395067d 100644 --- a/libs/components/src/callout/callout.component.spec.ts +++ b/libs/components/src/callout/callout.component.spec.ts @@ -56,5 +56,12 @@ describe("Callout", () => { expect(component.titleComputed()).toBe("Error"); expect(component.iconComputed()).toBe("bwi-error"); }); + + it("default", () => { + fixture.componentRef.setInput("type", "default"); + fixture.detectChanges(); + expect(component.titleComputed()).toBeUndefined(); + expect(component.iconComputed()).toBe("bwi-star"); + }); }); }); diff --git a/libs/components/src/callout/callout.component.ts b/libs/components/src/callout/callout.component.ts index 99a6c2aa123..62321a34d91 100644 --- a/libs/components/src/callout/callout.component.ts +++ b/libs/components/src/callout/callout.component.ts @@ -5,13 +5,14 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { SharedModule } from "../shared"; import { TypographyModule } from "../typography"; -export type CalloutTypes = "success" | "info" | "warning" | "danger"; +export type CalloutTypes = "success" | "info" | "warning" | "danger" | "default"; const defaultIcon: Record<CalloutTypes, string> = { success: "bwi-check-circle", info: "bwi-info-circle", warning: "bwi-exclamation-triangle", danger: "bwi-error", + default: "bwi-star", }; const defaultI18n: Partial<Record<CalloutTypes, string>> = { @@ -55,13 +56,15 @@ export class CalloutComponent { protected readonly calloutClass = computed(() => { switch (this.type()) { case "danger": - return "tw-bg-danger-100"; + return "tw-bg-danger-100 tw-border-danger-700 tw-text-danger-700"; case "info": - return "tw-bg-info-100"; + return "tw-bg-info-100 tw-bg-info-100 tw-border-info-700 tw-text-info-700"; case "success": - return "tw-bg-success-100"; + return "tw-bg-success-100 tw-bg-success-100 tw-border-success-700 tw-text-success-700"; case "warning": - return "tw-bg-warning-100"; + return "tw-bg-warning-100 tw-bg-warning-100 tw-border-warning-700 tw-text-warning-700"; + case "default": + return "tw-bg-background-alt tw-border-secondary-700 tw-text-secondary-700"; } }); } diff --git a/libs/components/src/callout/callout.mdx b/libs/components/src/callout/callout.mdx index a1254b3f691..297a2ffd0a3 100644 --- a/libs/components/src/callout/callout.mdx +++ b/libs/components/src/callout/callout.mdx @@ -41,6 +41,12 @@ automatically be checked. <Canvas of={stories.Info} /> +### Default + +Use for similar cases as the info callout but when content does not need to be as prominent. + +<Canvas of={stories.Default} /> + ### Warning Use a warning callout if the user is about to perform an action that may have unintended or @@ -67,4 +73,8 @@ Use the `role=”alert”` only if the callout is appearing on a page after the the content is static, do not use the alert role. This will cause a screen reader to announce the callout content on page load. -Ensure the title's color contrast remains WCAG compliant with the callout's background. +Ensure color contrast remains WCAG compliant with the callout's background. This is especially +important when adding `bit-link` or `bit-button` to the content area since the callout background is +colored. Currently only the `info` and `default` callouts are WCAG compliant for the `primary` +styling of these elements. The `secondary` `bit-link` styling may be used with the remaining +variants. diff --git a/libs/components/src/callout/callout.stories.ts b/libs/components/src/callout/callout.stories.ts index 4ac4191ce7e..c2185203034 100644 --- a/libs/components/src/callout/callout.stories.ts +++ b/libs/components/src/callout/callout.stories.ts @@ -1,6 +1,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LinkModule, IconModule } from "@bitwarden/components"; import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -12,6 +13,7 @@ export default { component: CalloutComponent, decorators: [ moduleMetadata({ + imports: [LinkModule, IconModule], providers: [ { provide: I18nService, @@ -69,6 +71,14 @@ export const Danger: Story = { }, }; +export const Default: Story = { + ...Info, + args: { + ...Info.args, + type: "default", + }, +}; + export const CustomIcon: Story = { ...Info, args: { @@ -80,6 +90,35 @@ export const CustomIcon: Story = { export const NoTitle: Story = { ...Info, args: { - icon: "bwi-star", + icon: "", + }, +}; + +export const NoTitleWithIcon: Story = { + render: (args) => ({ + props: args, + template: ` + <bit-callout ${formatArgsForCodeSnippet<CalloutComponent>(args)}>The content of the callout</bit-callout> + `, + }), + args: { + type: "default", + icon: "bwi-globe", + }, +}; + +export const WithTextButton: Story = { + render: (args) => ({ + props: args, + template: ` + <bit-callout ${formatArgsForCodeSnippet<CalloutComponent>(args)}> + <p class="tw-mb-2">The content of the callout</p> + <a bitLink> Visit the help center<i aria-hidden="true" class="bwi bwi-fw bwi-sm bwi-angle-right"></i> </a> + </bit-callout> + `, + }), + args: { + type: "default", + icon: "", }, }; diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index 62425bba7b3..b523c11c7e3 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -12,7 +12,6 @@ </bit-callout> <bit-callout *ngIf="hasLoginUri && hadPendingChangePasswordTask" type="warning" [title]="''"> - <i class="bwi bwi-exclamation-triangle tw-text-warning" aria-hidden="true"></i> <a bitLink href="#" appStopClick (click)="launchChangePassword()"> {{ "changeAtRiskPassword" | i18n }} <i class="bwi bwi-external-link tw-ml-1" aria-hidden="true"></i> From f442baeba105f2fc5362f9e1e1904f0da2b1d0ab Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 09:36:09 +0200 Subject: [PATCH 30/83] Autosync the updated translations (#16693) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 9 +- apps/browser/src/_locales/az/messages.json | 9 +- apps/browser/src/_locales/be/messages.json | 9 +- apps/browser/src/_locales/bg/messages.json | 9 +- apps/browser/src/_locales/bn/messages.json | 9 +- apps/browser/src/_locales/bs/messages.json | 9 +- apps/browser/src/_locales/ca/messages.json | 9 +- apps/browser/src/_locales/cs/messages.json | 9 +- apps/browser/src/_locales/cy/messages.json | 9 +- apps/browser/src/_locales/da/messages.json | 9 +- apps/browser/src/_locales/de/messages.json | 75 ++++++++------- apps/browser/src/_locales/el/messages.json | 9 +- apps/browser/src/_locales/en_GB/messages.json | 9 +- apps/browser/src/_locales/en_IN/messages.json | 9 +- apps/browser/src/_locales/es/messages.json | 9 +- apps/browser/src/_locales/et/messages.json | 9 +- apps/browser/src/_locales/eu/messages.json | 9 +- apps/browser/src/_locales/fa/messages.json | 9 +- apps/browser/src/_locales/fi/messages.json | 9 +- apps/browser/src/_locales/fil/messages.json | 9 +- apps/browser/src/_locales/fr/messages.json | 9 +- apps/browser/src/_locales/gl/messages.json | 9 +- apps/browser/src/_locales/he/messages.json | 9 +- apps/browser/src/_locales/hi/messages.json | 9 +- apps/browser/src/_locales/hr/messages.json | 9 +- apps/browser/src/_locales/hu/messages.json | 91 ++++++++++--------- apps/browser/src/_locales/id/messages.json | 9 +- apps/browser/src/_locales/it/messages.json | 9 +- apps/browser/src/_locales/ja/messages.json | 9 +- apps/browser/src/_locales/ka/messages.json | 9 +- apps/browser/src/_locales/km/messages.json | 9 +- apps/browser/src/_locales/kn/messages.json | 9 +- apps/browser/src/_locales/ko/messages.json | 9 +- apps/browser/src/_locales/lt/messages.json | 9 +- apps/browser/src/_locales/lv/messages.json | 9 +- apps/browser/src/_locales/ml/messages.json | 9 +- apps/browser/src/_locales/mr/messages.json | 9 +- apps/browser/src/_locales/my/messages.json | 9 +- apps/browser/src/_locales/nb/messages.json | 9 +- apps/browser/src/_locales/ne/messages.json | 9 +- apps/browser/src/_locales/nl/messages.json | 13 ++- apps/browser/src/_locales/nn/messages.json | 9 +- apps/browser/src/_locales/or/messages.json | 9 +- apps/browser/src/_locales/pl/messages.json | 9 +- apps/browser/src/_locales/pt_BR/messages.json | 9 +- apps/browser/src/_locales/pt_PT/messages.json | 9 +- apps/browser/src/_locales/ro/messages.json | 9 +- apps/browser/src/_locales/ru/messages.json | 9 +- apps/browser/src/_locales/si/messages.json | 9 +- apps/browser/src/_locales/sk/messages.json | 9 +- apps/browser/src/_locales/sl/messages.json | 9 +- apps/browser/src/_locales/sr/messages.json | 9 +- apps/browser/src/_locales/sv/messages.json | 13 ++- apps/browser/src/_locales/ta/messages.json | 9 +- apps/browser/src/_locales/te/messages.json | 9 +- apps/browser/src/_locales/th/messages.json | 9 +- apps/browser/src/_locales/tr/messages.json | 9 +- apps/browser/src/_locales/uk/messages.json | 9 +- apps/browser/src/_locales/vi/messages.json | 9 +- apps/browser/src/_locales/zh_CN/messages.json | 13 ++- apps/browser/src/_locales/zh_TW/messages.json | 21 +++-- apps/browser/store/locales/hu/copy.resx | 6 +- 62 files changed, 516 insertions(+), 211 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 397ea877cb5..84cece88d16 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 639e6c87d36..cc13e47a187 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Axtarışı sıfırla" }, - "archive": { - "message": "Arxivlə" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Arxivdən çıxart" diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 44c82ef85b4..c5a94d223e2 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index a440690cee1..b9089a0e807 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Нулиране на търсенето" }, - "archive": { - "message": "Архивиране" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Изваждане от архива" diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index e7c4c36bce0..db306f0cfe6 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index d9003a749a6..c33d183c1e7 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 2002dfc467f..8abbf14100d 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Restableix la cerca" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 0638257d687..59d17582c3d 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Resetovat hledání" }, - "archive": { - "message": "Archivovat" + "archiveNoun": { + "message": "Archiv", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archivovat", + "description": "Verb" }, "unarchive": { "message": "Odebrat z archivu" diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 8756a138e81..f129b8ce771 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index a78ff26fb0f..452d9d9423c 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index f04ca5b11be..fc3707f03ee 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Suche zurücksetzen" }, - "archive": { - "message": "Archivieren" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Archivierung aufheben" @@ -563,7 +568,7 @@ "message": "Kein Eintrag im Archiv" }, "noItemsInArchiveDesc": { - "message": "Archivierte Einträge erscheinen hier und werden von allgemeinen Suchergebnissen und Autofill Vorschlägen ausgeschlossen." + "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Autofill-Vorschlägen ausgeschlossen." }, "itemSentToArchive": { "message": "Eintrag an das Archiv gesendet" @@ -575,7 +580,7 @@ "message": "Eintrag archivieren" }, "archiveItemConfirmDesc": { - "message": "Archivierte Einträge sind von allgemeinen Suchergebnissen und Autofill Vorschlägen ausgeschlossen. Sind Sie sicher, dass Sie diesen Eintrag archivieren möchten?" + "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen und Autofill-Vorschlägen ausgeschlossen. Sind Sie sicher, dass Sie diesen Eintrag archivieren möchten?" }, "edit": { "message": "Bearbeiten" @@ -920,7 +925,7 @@ "message": "Folge den Schritten unten, um die Anmeldung abzuschließen." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Folge den Schritten unten, um die Anmeldung mit deinem Sicherheitsschlüssel abzuschließen." + "message": "Folge den untenstehenden Schritten, um die Anmeldung mit deinem Sicherheitsschlüssel abzuschließen." }, "restartRegistration": { "message": "Registrierung neu starten" @@ -1096,7 +1101,7 @@ "message": "Speichern" }, "notificationViewAria": { - "message": "$ITEMNAME$ anzeigen, öffnet sich in neuem Fenster", + "message": "$ITEMNAME$ Ansicht, wird in einem neuen Fenster geöffnet", "placeholders": { "itemName": { "content": "$1" @@ -1105,7 +1110,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "Neuer Eintrag, öffnet sich in neuem Fenster", + "message": "Neuer Eintrag, wird in einem neuen Fenster geöffnet", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1207,10 +1212,10 @@ "description": "Detailed error message shown when saving login details fails." }, "changePasswordWarning": { - "message": "Nachdem du dein Passwort geändert hast, musst du dich mit deinem neuen Passwort anmelden. Aktive Sitzungen auf anderen Geräten werden innerhalb einer Stunde abgemeldet." + "message": "Nachdem Sie Ihr Passwort geändert haben, müssen Sie sich mit Ihrem neuen Passwort anmelden. Aktive Sitzungen auf anderen Geräten werden innerhalb einer Stunde abgemeldet." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Ändere dein Master-Passwort, um die Kontowiederherstellung abzuschließen." + "message": "Ändern Sie Ihr Master-Passwort, um die Konto­wiederherstellung abzuschließen." }, "enableChangedPasswordNotification": { "message": "Nach dem Aktualisieren bestehender Zugangsdaten fragen" @@ -1405,7 +1410,7 @@ "message": "Funktion nicht verfügbar" }, "legacyEncryptionUnsupported": { - "message": "Die veraltete Verschlüsselung wird nicht mehr unterstützt. Bitte kontaktiere den Support, um dein Konto wiederherzustellen." + "message": "Alte Verschlüsselung wird nicht mehr unterstützt. Bitte wenden Sie sich an den Support, um Ihr Konto wiederherzustellen." }, "premiumMembership": { "message": "Premium-Mitgliedschaft" @@ -1636,10 +1641,10 @@ "message": "Vorschläge zum Auto-Ausfüllen" }, "autofillSpotlightTitle": { - "message": "Auto-Ausfüllen-Vorschläge einfach finden" + "message": "Autofill-Vorschläge leicht finden" }, "autofillSpotlightDesc": { - "message": "Deaktiviere die Auto-Ausfüllen-Einstellungen deines Browsers, damit sie nicht mit Bitwarden in Konflikt geraten." + "message": "Deaktivieren Sie die Autofill-Einstellungen des Browsers, damit sie nicht mit Bitwarden in Konflikt geraten." }, "turnOffBrowserAutofill": { "message": "$BROWSER$ Auto-Ausfüllen deaktivieren", @@ -1782,7 +1787,7 @@ "message": "Dieses Pop-up Fenster wird geschlossen, wenn du außerhalb des Fensters klickst um in deinen E-Mails nach dem Verifizierungscode zu suchen. Möchtest du, dass dieses Pop-up in einem separaten Fenster geöffnet wird, damit es nicht geschlossen wird?" }, "showIconsChangePasswordUrls": { - "message": "Website Symbole anzeigen und URLs zum Ändern von Passwörtern abrufen" + "message": "Website-Symbole anzeigen und URLs zum Ändern von Passwörtern abrufen" }, "cardholderName": { "message": "Name des Karteninhabers" @@ -2227,7 +2232,7 @@ "message": "Gebe deinen PIN-Code für das Entsperren von Bitwarden ein. Deine PIN-Einstellungen werden zurückgesetzt, wenn du dich vollständig von der Anwendung abmeldest." }, "setPinCode": { - "message": "Du kannst diese PIN verwenden, um Bitwarden zu entsperren. Deine PIN wird zurückgesetzt, wenn du dich vollständig aus der Anwendung abmeldest." + "message": "Sie können diese PIN verwenden, um Bitwarden zu entsperren. Ihre PIN wird zurückgesetzt, wenn Sie sich jemals vollständig von der Anwendung abmelden." }, "pinRequired": { "message": "PIN-Code ist erforderlich." @@ -2561,7 +2566,7 @@ "message": "Karten-Eintragstypen können nicht importiert werden" }, "restrictCardTypeImportDesc": { - "message": "Eine von einer oder mehreren Organisationen festgelegte Richtlinie verhindert, dass du Karten in deinen Tresor importieren kannst." + "message": "Eine von 1 oder mehreren Organisationen festgelegte Richtlinie verhindert den Import von Karten in Ihre Tresore." }, "domainsTitle": { "message": "Domains", @@ -2645,7 +2650,7 @@ } }, "atRiskChangePrompt": { - "message": "Dein Passwort für diese Website ist gefährdet. $ORGANIZATION$ hat darum gebeten, dass du es änderst.", + "message": "Ihr Passwort für diese Website ist gefährdet. $ORGANIZATION$ hat Sie aufgefordert, es zu ändern.", "placeholders": { "organization": { "content": "$1", @@ -2655,7 +2660,7 @@ "description": "Notification body when a login triggers an at-risk password change request and the change password domain is known." }, "atRiskNavigatePrompt": { - "message": "$ORGANIZATION$ möchte, dass du dieses Passwort änderst, da es gefährdet ist. Wechsel zu deinen Kontoeinstellungen, um das Passwort zu ändern.", + "message": "$ORGANIZATION$ möchte, dass Sie dieses Passwort ändern, da es gefährdet ist. Gehen Sie zu Ihren Kontoeinstellungen, um das Passwort zu ändern.", "placeholders": { "organization": { "content": "$1", @@ -3538,7 +3543,7 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Du hast einen Anmeldeversuch von einem anderen Gerät abgelehnt. Wenn du das wirklich warst, versuche dich erneut mit dem Gerät anzumelden." + "message": "Sie haben einen Versuch mit Zugangsdaten von einem anderen Gerät abgelehnt. Wenn Sie das waren, versuchen Sie, sich mit dem Gerät erneut anzumelden." }, "device": { "message": "Gerät" @@ -3752,7 +3757,7 @@ "message": "Anmeldung kann nicht abgeschlossen werden" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "Du musst dich auf einem vertrauenswürdigen Gerät anmelden oder deinem Administrator bitten, dir ein Passwort zuzuweisen." + "message": "Sie müssen sich auf einem vertrauenswürdigen Gerät anmelden oder Ihren Administrator bitten, Ihnen ein Passwort zuzuweisen." }, "ssoIdentifierRequired": { "message": "SSO-Kennung der Organisation erforderlich." @@ -3828,13 +3833,13 @@ "message": "Organisation ist nicht vertrauenswürdig" }, "emergencyAccessTrustWarning": { - "message": "Bestätige zur Sicherheit deines Kontos nur, wenn du den Notfallzugriff diesem Benutzer gewährt hast und sein Fingerabdruck mit dem übereinstimmt, was in seinem Konto angezeigt wird" + "message": "Zur Sicherheit Ihres Kontos bestätigen Sie nur, wenn Sie diesem Benutzer Notfallzugriff gewährt haben und sein Fingerabdruck mit dem in seinem Konto angezeigten übereinstimmt" }, "orgTrustWarning": { - "message": "Fahre zur Sicherheit deines Kontos nur fort, wenn du ein Mitglied dieser Organisation bist, die Kontowiederherstellung aktiviert hast und der unten angezeigte Fingerabdruck mit dem Fingerabdruck der Organisation übereinstimmt." + "message": "Zur Sicherheit Ihres Kontos fahren Sie nur fort, wenn Sie Mitglied dieser Organisation sind, die Kontowiederherstellung aktiviert haben und der unten angezeigte Fingerabdruck mit dem Fingerabdruck der Organisation übereinstimmt." }, "orgTrustWarning1": { - "message": "Diese Organisation hat eine Unternehmensrichtlinie, die dich für die Kontowiederherstellung registriert. Die Registrierung wird es den Administratoren der Organisation erlauben, dein Passwort zu ändern. Fahre nur fort, wenn du diese Organisation kennst und die unten angezeigte Fingerabdruck-Phrase mit der der Organisation übereinstimmt." + "message": "Diese Organisation hat eine Enterprise-Richtlinie, die Sie in die Kontowiederherstellung registriert. Die Registrierung ermöglicht es den Administratoren der Organisation, Ihr Passwort zu ändern. Fahren Sie nur fort, wenn Sie diese Organisation erkennen und die unten angezeigte Fingerabdruck-Phrase mit dem Fingerabdruck der Organisation übereinstimmt." }, "trustUser": { "message": "Benutzer vertrauen" @@ -5536,7 +5541,7 @@ "message": "Jetzt importieren" }, "hasItemsVaultNudgeTitle": { - "message": "Willkommen in deinem Tresor!" + "message": "Willkommen in Ihrem Tresor!" }, "phishingPageTitle": { "message": "Phishing Webseite" @@ -5557,7 +5562,7 @@ "message": "Favoriten-Einträge für einfachen Zugriff" }, "hasItemsVaultNudgeBodyThree": { - "message": "Deinen Tresor nach etwas anderem durchsuchen" + "message": "Durchsuchen Sie Ihren Tresor nach etwas anderem" }, "newLoginNudgeTitle": { "message": "Spare Zeit mit Auto-Ausfüllen" @@ -5581,30 +5586,30 @@ "message": "Nahtlose Online-Kaufabwicklung" }, "newCardNudgeBody": { - "message": "Mit Karten kannst du Zahlungsformulare sicher und präzise einfach automatisch ausfüllen." + "message": "Mit Karten können Sie Zahlungsformulare einfach, sicher und genau automatisch ausfüllen." }, "newIdentityNudgeTitle": { "message": "Erstellung von Konten vereinfachen" }, "newIdentityNudgeBody": { - "message": "Mit Identitäten kannst du lange Registrierungs- oder Kontaktformulare schnell automatisch ausfüllen." + "message": "Mit Identitäten können Sie lange Registrierungs- oder Kontaktformulare schnell per Autofill ausfüllen." }, "newNoteNudgeTitle": { - "message": "Bewahre deine sensiblen Daten sicher auf" + "message": "Halten Sie Ihre sensiblen Daten sicher" }, "newNoteNudgeBody": { - "message": "Mit Notizen speicherst du sensible Daten wie Bank- oder Versicherungs-Informationen." + "message": "Mit Notizen können Sie sensible Daten wie Bank- oder Versicherungsdaten sicher speichern." }, "newSshNudgeTitle": { "message": "Entwickler-freundlicher SSH-Zugriff" }, "newSshNudgeBodyOne": { - "message": "Speicher deine Schlüssel und verbinden dich mit dem SSH-Agenten für eine schnelle und verschlüsselte Authentifizierung.", + "message": "Speichern Sie Ihre Schlüssel und verbinden Sie sich mit dem SSH-Agenten für eine schnelle, verschlüsselte Authentifizierung.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Erfahre mehr über den SSH-Agenten", + "message": "Erfahren Sie mehr über den SSH-Agenten", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, @@ -5617,25 +5622,25 @@ "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyTwo": { - "message": ", um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "message": "damit Sie Ihre Zugangsdaten sicher halten können.", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "message": "Erstellen Sie ganz einfach starke und eindeutige Passwörter, indem Sie auf die Schaltfläche ‚Passwort generieren‘ klicken, damit Sie Ihre Zugangsdaten sicher halten können.", "description": "Aria label for the body content of the generator nudge" }, "aboutThisSetting": { "message": "Über diese Einstellung" }, "permitCipherDetailsDescription": { - "message": "Bitwarden wird gespeicherte Login-URIs verwenden, um zu ermitteln, welches Symbol oder welche URL zum Ändern des Passworts verwendet werden soll, um Ihr Erlebnis zu verbessern. Bei der Nutzung dieses Dienstes werden keine Informationen erfasst oder gespeichert." + "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu bestimmen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um Ihr Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn Sie diesen Dienst nutzen." }, "noPermissionsViewPage": { - "message": "Du hast keine Berechtigung, diese Seite anzuzeigen. Versuche dich mit einem anderen Konto anzumelden." + "message": "Sie haben keine Berechtigung, diese Seite anzusehen. Versuchen Sie, sich mit einem anderen Konto anzumelden." }, "wasmNotSupported": { - "message": "WebAssembly wird von deinem Browser nicht unterstützt oder ist nicht aktiviert. WebAssembly wird benötigt, um die Bitwarden-App nutzen zu können.", + "message": "WebAssembly wird in Ihrem Browser nicht unterstützt oder ist nicht aktiviert. WebAssembly ist erforderlich, um die Bitwarden-App zu verwenden.", "description": "'WebAssembly' is a technical term and should not be translated." }, "showMore": { diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index cce3e0ea39f..9c23b511611 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Επαναφορά αναζήτησης" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 43bb17c297f..be548498512 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 59c4966a48c..b6f175afcfe 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index d3c6e3556a0..95d31915e2d 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Restablecer búsqueda" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 5508a1cee72..e50aad7c70f 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 93242263dc0..a6abe270e38 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 129f2ee383a..d8634a88bd0 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 5de1d9fe7e4..cfe4bc3504d 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Nollaa haku" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 600abfb2d4e..678cf714915 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 765ebff53c5..031bca7c44c 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Réinitialiser la recherche" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index c2573ea6bfa..217c3d37176 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 38fe3618610..d2a8db5fdd7 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "אפס חיפוש" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 1575543aef3..35e69470e4c 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "खोज रीसेट करें" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 4f67de34071..bb76ea74b82 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Ponovno postavljanje pretraživanja" }, - "archive": { - "message": "Arhiviraj" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Poništi arhiviranje" diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 864580a64b0..6717b18d38e 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Keresés visszaállítása" }, - "archive": { - "message": "Archívum" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Visszavétel archívumból" @@ -1654,7 +1659,7 @@ "message": "Automat kitöltés bekapcsolása" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "Automatikus kitöltési javaslatok megjelenítése űrlapmezőknél" }, "showInlineMenuIdentitiesLabel": { "message": "Az identitások megjelenítése javaslatként" @@ -1663,10 +1668,10 @@ "message": "A kártyák megjelenítése javaslatként" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Javaslatok megjelenítése a az ikon kiválasztásakor" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Minden bejelentkezett fiókra vonatkozik." }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "Az ütközések elkerülése érdekében kapcsoljuk ki a böngésző beépített jelszókezelő beállításait." @@ -1679,7 +1684,7 @@ "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "Amikor a mező kiválasztásra kerül (fókuszoláskor)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { @@ -1687,7 +1692,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "Automatikus kitöltés az oldal betöltésénél" }, "enableAutoFillOnPageLoad": { "message": "Automatikus kitöltés engedélyezése oldal betöltéskor" @@ -1699,7 +1704,7 @@ "message": "Az oldalbetöltésnél automatikus kitöltést a feltört vagy nem megbízhatató weboldalak kihasználhatják." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "Bővebben a kockázatokról" }, "learnMoreAboutAutofill": { "message": "További információk az automatikus kitöltésről" @@ -2423,7 +2428,7 @@ "message": "Az új mesterjelszó nem felel meg a szabály követelményeknek." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Tanácsokat, bejelentéseket, kutatási lehetőségeket kaphat a Bitwarden-től a postaládájába." }, "unsubscribe": { "message": "Leiratkozás" @@ -2459,10 +2464,10 @@ "message": "Ok" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Hozzáférési Token Frissítés Hiba" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Nem található token vagy API kulcs. Próbáljon meg ki-, majd újra bejelentkezni." }, "desktopSyncVerificationTitle": { "message": "Asztali szinkronizálás ellenőrzés" @@ -3583,7 +3588,7 @@ "message": "A szervezeti szabályzat bekapcsolta az automatikus kitöltést az oldalbetöltéskor." }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "Válasszon ki egy elemet a képernyőről, használja a $COMMAND$ kombinációt, vagy tekintse meg a többi lehetőséget a beállításokban.", "placeholders": { "command": { "content": "$1", @@ -3592,7 +3597,7 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "Válasszon ki egy elemet a képernyőről, vagy tekintse meg a többi lehetőséget a beállításokban." }, "gotIt": { "message": "Rendben" @@ -3601,10 +3606,10 @@ "message": "Automatikus kitöltés beállítások" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "Automatikus kitöltés gyorselérés" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "Billentyűparancs változtatás" }, "autofillKeyboardManagerShortcutsLabel": { "message": "Bullenytűparancsok kezelése" @@ -3971,17 +3976,17 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Oldalnavigáció váltás" }, "skipToContent": { "message": "Ugrás a tartalomra" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "Bitwarden automatikus kitöltés menü gomb", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "Bitwarden automatikus kitöltés menü váltás", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { @@ -3989,7 +3994,7 @@ "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "Az összeillő belépések megtekintéséhez oldja fel fiókját", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { @@ -4057,7 +4062,7 @@ "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden autofill menu available. Press the down arrow key to select.", + "message": "Bitwarden automatikus kitöltés menü elérhető. Nyomja meg a lefele nyilat a kiválasztáshoz.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -4143,7 +4148,7 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Hiba a Duo szolgáltatáshoz való kapcsolódáskor. Használjon másféle kétlépcsős bejelentkezést, vagy keresse fel a Duo ügyfélszolgálatot." }, "duoRequiredForAccount": { "message": "A fiókhoz kétlépcsős DUO bejelentkezés szükséges." @@ -4439,27 +4444,27 @@ "description": "Advanced option placeholder for uri option component" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "Továbblépés a böngésző beállításokhoz?", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "Továbblépés a Segítség Központba?", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "A böngésző automatikus kitöltés és jelszókezelési beállításainak módosítása.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "Megtekintheti vagy beállíthatja a bővítmény billentyűparancsokat a böngésző beállításoknál.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "A böngésző automatikus kitöltési és jelszókezelési beállításainak módosítása.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "Megtekintheti vagy beállíthatja a bővítmény billentyűparancsokat a böngésző beállításoknál.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { @@ -4639,7 +4644,7 @@ "message": "Nincsenek másolandó értékek." }, "assignToCollections": { - "message": "Assign to collections" + "message": "Hozzárendelés gyűjteményhez" }, "copyEmail": { "message": "Email cím másolása" @@ -4702,7 +4707,7 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "Be nem mappázott elemek" }, "itemDetails": { "message": "Elem részletek" @@ -4711,7 +4716,7 @@ "message": "Elem neve" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "A szervezet deaktiválásra került" }, "owner": { "message": "Tulajdonos" @@ -4721,7 +4726,7 @@ "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "A deaktivált szervezetek elemeit nem lehet elérni. Keresse fel további segítségért a szervezet tulajdonosát." }, "additionalInformation": { "message": "További információ" @@ -4949,7 +4954,7 @@ "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "Hozzárendelés" }, "bulkCollectionAssignmentDialogDescriptionSingular": { "message": "Csak az ezekhez a gyűjteményekhez hozzáféréssel rendelkező szervezeti tagok láthatják az elemet." @@ -4958,7 +4963,7 @@ "message": "Csak az ezekhez a gyűjteményekhez hozzáféréssel rendelkező szervezeti tagok láthatják az elemeket." }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "$TOTAL_COUNT$ elemeket jelölt ki. Nem frissítheti a $READONLY_COUNT$ részét, mert nem rendelkezik szerkesztési jogosultsággal.", "placeholders": { "total_count": { "content": "$1", @@ -5056,13 +5061,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "A hozzárendeléshez jelöljön ki gyűjteményeket" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 elem véglegesen áthelyezésre kerül a szervezethez. Többé nem Önhöz fog tartozni az elem." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ elemek véglegesen áthelyezésre kerülnek a kiválasztott szervezethez. Többé nem Önhöz fognak tartozni az elemek.", "placeholders": { "personal_items_count": { "content": "$1", @@ -5071,7 +5076,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "Ez az 1 elem véglegesen áthelyezésre kerül a $ORG$ szervezethez. Többé nem az Öné lesz az elem.", "placeholders": { "org": { "content": "$1", @@ -5080,7 +5085,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ elem véglegesen áthelyezésre kerül a $ORG$ szervezethez. Többé nem az Öné lesz az elem.", "placeholders": { "personal_items_count": { "content": "$1", @@ -5093,13 +5098,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "Sikerült a gyűjteményhez való hozzárendelés" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "Nem választott ki semmit." }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "Elemek áthelyezve a $ORGNAME$ szervezethez", "placeholders": { "orgname": { "content": "$1", @@ -5108,7 +5113,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "Elem áthelyezve a $ORGNAME$ szervezethez", "placeholders": { "orgname": { "content": "$1", @@ -5269,7 +5274,7 @@ "message": "Feloldás biometrikusan" }, "authenticating": { - "message": "Authenticating" + "message": "Hitelesítés" }, "fillGeneratedPassword": { "message": "Generált jelszó kitöltés", diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index b38b6f05628..9d3c2817bf1 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Atur ulang pencarian" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index df4411ee42b..ccabe3cb55c 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Svuota ricerca" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 5305a265781..df19cf23bc3 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index b759d674cca..b0c65bc8a37 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 78a49021a0c..9646960000a 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 1311a97df68..94f276e5b73 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 06611be0282..bffa0c34ee6 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 464fa5aae92..55555fe9b4f 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 99edb486d9d..a70aded2ed1 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Atiestatīt meklēšanu" }, - "archive": { - "message": "Arhivēt" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Atcelt arhivēšanu" diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index efe18c96a59..4ebcd08bb77 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 16ac31ff599..4bc66b4e85b 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 78a49021a0c..9646960000a 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index a23fd7fe4c1..113c28c65d3 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 78a49021a0c..9646960000a 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 2562b7a1d4c..155886506b0 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Zoekopdracht resetten" }, - "archive": { - "message": "Archiveren" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Dearchiveren" @@ -3432,10 +3437,10 @@ "message": "Premium-abonnement vereist" }, "organizationIsDisabled": { - "message": "Organisatie is uitgeschakeld." + "message": "Organisatie opgeschort." }, "disabledOrganizationFilterError": { - "message": "Je kunt uitgeschakelde items in een organisatie niet benaderen. Neem contact op met de eigenaar van je organisatie voor hulp." + "message": "Je kunt items in opgeschorte organisaties niet benaderen. Neem contact op met de eigenaar van je organisatie voor hulp." }, "loggingInTo": { "message": "Inloggen op $DOMAIN$", diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 78a49021a0c..9646960000a 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 78a49021a0c..9646960000a 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index f24e790c9ad..e455a0c7d2e 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Zresetuj wyszukiwanie" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Usuń z archiwum" diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 2d7dd1e42a4..0161aa77ea7 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index acc5b5332f9..cc90d7653c9 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Repor pesquisa" }, - "archive": { - "message": "Arquivar" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Desarquivar" diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index d184460e293..4cc552b11ab 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 17133350e3f..49cdcf62161 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Сбросить поиск" }, - "archive": { - "message": "Архив" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Разархивировать" diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 2fd8f53e148..6153cb49b6e 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index d0e143cce4a..da1ba9427ea 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Resetovať vyhľadávanie" }, - "archive": { - "message": "Archivovať" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Zrušiť archiváciu" diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 81b1a6bb52c..596ee118eab 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 269ebd41bdd..c3c9cac383d 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Ресетовати претрагу" }, - "archive": { - "message": "Архива" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Врати из архиве" diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 8b0263bf15a..02b334d9412 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Nollställ sökning" }, - "archive": { - "message": "Arkivera" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Packa upp" @@ -566,10 +571,10 @@ "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." }, "itemSentToArchive": { - "message": "Item sent to archive" + "message": "Objekt skickat till arkiv" }, "itemRemovedFromArchive": { - "message": "Item removed from archive" + "message": "Objekt borttaget från arkiv" }, "archiveItem": { "message": "Arkivera objekt" diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index 8d2199db6ca..c5a13db193e 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "தேடலை மீட்டமை" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 78a49021a0c..9646960000a 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 61f97564f6a..4eed06d0a9a 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Reset search" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 0b65ae7d476..f2acc401a2c 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Aramayı sıfırla" }, - "archive": { - "message": "Arşivle" + "archiveNoun": { + "message": "Arşiv", + "description": "Noun" + }, + "archiveVerb": { + "message": "Arşivle", + "description": "Verb" }, "unarchive": { "message": "Arşivden çıkar" diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index f088e610051..a99c585978b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Скинути пошук" }, - "archive": { - "message": "Архівувати" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Видобути" diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 06920433037..3a11f0f237f 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "Đặt lại tìm kiếm" }, - "archive": { - "message": "Lưu trữ" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Hủy lưu trữ" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index eb3cf1aa901..77a3855633c 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "重置搜索" }, - "archive": { - "message": "归档" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "取消归档" @@ -1134,7 +1139,7 @@ "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "选择 $ITEMTYPE$,$ITEMNAME$", + "message": "选择 $ITEMNAME$ 中的 $ITEMTYPE$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -4622,7 +4627,7 @@ } }, "copyFieldCipherName": { - "message": "复制 $CIPHERNAME$ 的 $FIELD$", + "message": "复制 $CIPHERNAME$ 中的 $FIELD$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index f5801fb2c7d..23564473318 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -550,8 +550,13 @@ "resetSearch": { "message": "重設搜尋" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" @@ -3541,7 +3546,7 @@ "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." }, "device": { - "message": "Device" + "message": "裝置" }, "loginStatus": { "message": "Login status" @@ -3640,7 +3645,7 @@ "message": "記住此裝置來讓未來的登入體驗更簡易" }, "manageDevices": { - "message": "Manage devices" + "message": "管理裝置" }, "currentSession": { "message": "Current session" @@ -3683,7 +3688,7 @@ "message": "Needs approval" }, "devices": { - "message": "Devices" + "message": "裝置" }, "accessAttemptBy": { "message": "Access attempt by $EMAIL$", @@ -3704,7 +3709,7 @@ "message": "Time" }, "deviceType": { - "message": "Device Type" + "message": "裝置類型" }, "loginRequest": { "message": "Login request" @@ -5434,10 +5439,10 @@ "message": "擴充套件寬度" }, "wide": { - "message": "寬度" + "message": "寬" }, "extraWide": { - "message": "更寬" + "message": "超寬" }, "sshKeyWrongPassword": { "message": "The password you entered is incorrect." diff --git a/apps/browser/store/locales/hu/copy.resx b/apps/browser/store/locales/hu/copy.resx index 814ebabaada..e3a5c733eb5 100644 --- a/apps/browser/store/locales/hu/copy.resx +++ b/apps/browser/store/locales/hu/copy.resx @@ -118,10 +118,10 @@ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="Name" xml:space="preserve"> - <value>Bitwarden Password Manager</value> + <value>Bitwarden Jelszókezelő</value> </data> <data name="Summary" xml:space="preserve"> - <value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value> + <value>Legyen otthon, munkában, vagy úton, a Bitwarden könnyen biztosítja jelszavát, kulcsait, és kényes információit.</value> </data> <data name="Description" xml:space="preserve"> <value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! @@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga </value> </data> <data name="AssetTitle" xml:space="preserve"> - <value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value> + <value>Legyen otthon, munkában, vagy úton, a Bitwarden könnyen biztosítja jelszavát, kulcsait, és kényes információit.</value> </data> <data name="ScreenshotSync" xml:space="preserve"> <value>A széf szinkronizálása és elérése több eszközön.</value> From 3ec9087e7e09c881dbb8c3626d644a8b0b0f41f9 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 09:41:47 +0200 Subject: [PATCH 31/83] Autosync the updated translations (#16692) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 25 +++++-- apps/desktop/src/locales/ar/messages.json | 25 +++++-- apps/desktop/src/locales/az/messages.json | 25 +++++-- apps/desktop/src/locales/be/messages.json | 25 +++++-- apps/desktop/src/locales/bg/messages.json | 25 +++++-- apps/desktop/src/locales/bn/messages.json | 25 +++++-- apps/desktop/src/locales/bs/messages.json | 25 +++++-- apps/desktop/src/locales/ca/messages.json | 29 +++++--- apps/desktop/src/locales/cs/messages.json | 25 +++++-- apps/desktop/src/locales/cy/messages.json | 25 +++++-- apps/desktop/src/locales/da/messages.json | 25 +++++-- apps/desktop/src/locales/de/messages.json | 77 +++++++++++--------- apps/desktop/src/locales/el/messages.json | 25 +++++-- apps/desktop/src/locales/en_GB/messages.json | 25 +++++-- apps/desktop/src/locales/en_IN/messages.json | 25 +++++-- apps/desktop/src/locales/eo/messages.json | 25 +++++-- apps/desktop/src/locales/es/messages.json | 25 +++++-- apps/desktop/src/locales/et/messages.json | 25 +++++-- apps/desktop/src/locales/eu/messages.json | 25 +++++-- apps/desktop/src/locales/fa/messages.json | 25 +++++-- apps/desktop/src/locales/fi/messages.json | 25 +++++-- apps/desktop/src/locales/fil/messages.json | 25 +++++-- apps/desktop/src/locales/fr/messages.json | 25 +++++-- apps/desktop/src/locales/gl/messages.json | 25 +++++-- apps/desktop/src/locales/he/messages.json | 25 +++++-- apps/desktop/src/locales/hi/messages.json | 25 +++++-- apps/desktop/src/locales/hr/messages.json | 25 +++++-- apps/desktop/src/locales/hu/messages.json | 25 +++++-- apps/desktop/src/locales/id/messages.json | 25 +++++-- apps/desktop/src/locales/it/messages.json | 25 +++++-- apps/desktop/src/locales/ja/messages.json | 25 +++++-- apps/desktop/src/locales/ka/messages.json | 25 +++++-- apps/desktop/src/locales/km/messages.json | 25 +++++-- apps/desktop/src/locales/kn/messages.json | 25 +++++-- apps/desktop/src/locales/ko/messages.json | 25 +++++-- apps/desktop/src/locales/lt/messages.json | 25 +++++-- apps/desktop/src/locales/lv/messages.json | 25 +++++-- apps/desktop/src/locales/me/messages.json | 25 +++++-- apps/desktop/src/locales/ml/messages.json | 25 +++++-- apps/desktop/src/locales/mr/messages.json | 25 +++++-- apps/desktop/src/locales/my/messages.json | 25 +++++-- apps/desktop/src/locales/nb/messages.json | 25 +++++-- apps/desktop/src/locales/ne/messages.json | 25 +++++-- apps/desktop/src/locales/nl/messages.json | 25 +++++-- apps/desktop/src/locales/nn/messages.json | 25 +++++-- apps/desktop/src/locales/or/messages.json | 25 +++++-- apps/desktop/src/locales/pl/messages.json | 25 +++++-- apps/desktop/src/locales/pt_BR/messages.json | 25 +++++-- apps/desktop/src/locales/pt_PT/messages.json | 25 +++++-- apps/desktop/src/locales/ro/messages.json | 25 +++++-- apps/desktop/src/locales/ru/messages.json | 25 +++++-- apps/desktop/src/locales/si/messages.json | 25 +++++-- apps/desktop/src/locales/sk/messages.json | 25 +++++-- apps/desktop/src/locales/sl/messages.json | 25 +++++-- apps/desktop/src/locales/sr/messages.json | 25 +++++-- apps/desktop/src/locales/sv/messages.json | 25 +++++-- apps/desktop/src/locales/ta/messages.json | 25 +++++-- apps/desktop/src/locales/te/messages.json | 25 +++++-- apps/desktop/src/locales/th/messages.json | 25 +++++-- apps/desktop/src/locales/tr/messages.json | 25 +++++-- apps/desktop/src/locales/uk/messages.json | 25 +++++-- apps/desktop/src/locales/vi/messages.json | 25 +++++-- apps/desktop/src/locales/zh_CN/messages.json | 27 +++++-- apps/desktop/src/locales/zh_TW/messages.json | 27 +++++-- 64 files changed, 1182 insertions(+), 478 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index e579c498ded..128a2e81864 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 4efec524886..3dacae24389 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 9618bafca3e..27b94d34ee7 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Daha az göstər" }, - "enableAutotype": { - "message": "Avto-yazmanı fəallaşdır" - }, "enableAutotypeDescription": { "message": "Bitwarden, giriş yerlərini doğrulamır, qısayolu istifadə etməzdən əvvəl doğru pəncərədə və xanada olduğunuza əmin olun." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Daha çox naviqasiya yolu", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Təsdiqlə" }, - "enableAutotypeTransitionKey": { - "message": "Avto-yazma qısayolunu fəallaşdır" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Veriləri yanlış yerə doldurmamaq üçün qısayolu istifadə etməzdən əvvəl doğru xanada olduğunuza əmin olun." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Qısayola düzəliş et" }, - "archive": { - "message": "Arxivlə" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Arxivdən çıxart" diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index eb5971c97af..475ef8805f8 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index e32363f0c55..c40afa02073 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Показване на по-малко" }, - "enableAutotype": { - "message": "Включване на автоматичното въвеждане" - }, "enableAutotypeDescription": { "message": "Битуорден не проверява местата за въвеждане, така че се уверете, че сте в правилния прозорец, преди да ползвате клавишната комбинация." }, + "typeShortcut": { + "message": "Комбинация за въвеждане" + }, + "editAutotypeShortcutDescription": { + "message": "Използвайте един или повече от модификаторите Ctrl, Alt, Win или Shift, заедно с някоя буква." + }, + "invalidShortcut": { + "message": "Неправилна комбинация" + }, "moreBreadcrumbs": { "message": "Още елементи в пътечката", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Потвърждаване" }, - "enableAutotypeTransitionKey": { - "message": "Включване на клавишната комбинация за автоматично попълване" + "enableAutotypeShortcutPreview": { + "message": "Включване на комбинация за автоматично попълване (Функционалност в изпитание)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Уверете се, че сет в правилното поле, преди да използвате комбинацията, за да избегнете попълването на данните на грешното място." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Редактиране на комбинацията" }, - "archive": { - "message": "Архивиране" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Изваждане от архива" diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 60b925af2e3..c5dc6f5372f 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index e6cdff50696..b484970d59a 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 0defa7a878a..e3864cd7cc9 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -2371,7 +2371,7 @@ "message": "Autenticar WebAuthn" }, "readSecurityKey": { - "message": "Llegeix clau de seguretat" + "message": "Llig la clau de seguretat" }, "awaitingSecurityKeyInteraction": { "message": "S'està esperant la interacció amb la clau de seguretat..." @@ -2887,7 +2887,7 @@ } }, "forwarderNoDomain": { - "message": "Domini de $SERVICENAME$ invàlid.", + "message": "Domini de $SERVICENAME$ no vàlid.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 2c5ed437187..d0539502661 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Zobrazit méně" }, - "enableAutotype": { - "message": "Povolit automatický zápis" - }, "enableAutotypeDescription": { "message": "Bitwarden neověřuje umístění vstupu. Před použitím zkratky se ujistěte, že jste ve správném okně a poli." }, + "typeShortcut": { + "message": "Napsat zkratku" + }, + "editAutotypeShortcutDescription": { + "message": "Zahrňte jeden nebo dva z následujících modifikátorů: Ctrl, Alt, Win nebo Shift a písmeno." + }, + "invalidShortcut": { + "message": "Neplatná zkratka" + }, "moreBreadcrumbs": { "message": "Více...", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Potvrdit" }, - "enableAutotypeTransitionKey": { - "message": "Povolit zkratku automatického psaní" + "enableAutotypeShortcutPreview": { + "message": "Povolit zkratku Autotype (náhled funkce)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Před použitím zkratky se ujistěte, že jste ve správném poli, abyste se vyhnuli vyplnění dat na nesprávné místo." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Upravit zkratku" }, - "archive": { - "message": "Archivovat" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Odebrat z archivu" diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 9ff42bfa2c7..77063607b47 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 4a064a004cb..dcbd21d3fe9 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 47b3bad34e8..094024f1a3f 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -449,7 +449,7 @@ "message": "Feld bearbeiten" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Bist du sicher, dass du diesen Anhang dauerhaft löschen möchtest?" + "message": "Sind Sie sicher, dass Sie diesen Anhang dauerhaft löschen möchten?" }, "fieldType": { "message": "Feldtyp" @@ -703,10 +703,10 @@ "message": "Anhang gespeichert" }, "addAttachment": { - "message": "Add attachment" + "message": "Anhang hinzufügen" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "Die maximale Dateigröße beträgt 500 MB" }, "file": { "message": "Datei" @@ -1303,7 +1303,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "showIconsChangePasswordUrls": { - "message": "Show website icons and retrieve change password URLs" + "message": "Website-Symbole anzeigen und URLs zum Ändern von Passwörtern abrufen" }, "enableMinToTray": { "message": "In Infobereich-Symbol minimieren" @@ -3486,10 +3486,10 @@ "message": "Sammlung auswählen" }, "importTargetHintCollection": { - "message": "Select this option if you want the imported file contents moved to a collection" + "message": "Wählen Sie diese Option, wenn die Inhalte der Import-Datei in eine Sammlung verschoben werden sollen" }, "importTargetHintFolder": { - "message": "Select this option if you want the imported file contents moved to a folder" + "message": "Wählen Sie diese Option, wenn die Inhalte der Import-Datei in einen Ordner verschoben werden sollen" }, "importUnassignedItemsError": { "message": "Die Datei enthält nicht zugewiesene Einträge." @@ -3586,10 +3586,10 @@ "message": "Bitte melde dich weiterhin mit deinen Firmenzugangsdaten an." }, "importDirectlyFromBrowser": { - "message": "Import directly from browser" + "message": "Direkter Import aus dem Browser" }, "browserProfile": { - "message": "Browser Profile" + "message": "Browser-Profil" }, "seeDetailedInstructions": { "message": "Detaillierte Anleitungen auf unserer Hilfeseite unter", @@ -3834,10 +3834,10 @@ "message": "Gefährdetes Passwort ändern" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." + "message": "Diese Zugangsdaten sind gefährdet und enthalten keine Website. Fügen Sie eine Website hinzu und ändern Sie das Passwort für mehr Sicherheit." }, "missingWebsite": { - "message": "Missing website" + "message": "Fehlende Webseite" }, "cannotRemoveViewOnlyCollections": { "message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$", @@ -3935,10 +3935,10 @@ "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "aboutThisSetting": { - "message": "About this setting" + "message": "Über diese Einstellung" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu bestimmen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um Ihr Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn Sie diesen Dienst nutzen." }, "assignToCollections": { "message": "Sammlungen zuweisen" @@ -4080,59 +4080,70 @@ "showLess": { "message": "Weniger anzeigen" }, - "enableAutotype": { - "message": "Autotype aktivieren" - }, "enableAutotypeDescription": { "message": "Bitwarden überprüft die Eingabestellen nicht. Vergewissere dich, dass du dich im richtigen Fenster und Feld befindest, bevor du die Tastenkombination verwendest." }, + "typeShortcut": { + "message": "Typ-Verknüpfung" + }, + "editAutotypeShortcutDescription": { + "message": "Fügen Sie einen oder zwei der folgenden Modifikatoren ein: Strg, Alt, Win oder Umschalt, sowie einen Buchstaben." + }, + "invalidShortcut": { + "message": "Ungültige Verknüpfung" + }, "moreBreadcrumbs": { - "message": "More breadcrumbs", + "message": "Weitere Navigationspfade", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." }, "next": { - "message": "Next" + "message": "Weiter" }, "confirmKeyConnectorDomain": { - "message": "Confirm Key Connector domain" + "message": "Key Connector-Domain bestätigen" }, "confirm": { - "message": "Confirm" + "message": "Bestätigen" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Autotype-Shortcut aktivieren (Funktionsvorschau)" }, "enableAutotypeDescriptionTransitionKey": { - "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." + "message": "Stellen Sie sicher, dass Sie sich im richtigen Feld befinden, bevor Sie die Verknüpfung verwenden, um zu vermeiden, dass Daten an der falschen Stelle ausgefüllt werden." }, "editShortcut": { - "message": "Edit shortcut" + "message": "Verknüpfung bearbeiten" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { - "message": "Unarchive" + "message": "Archivierung aufheben" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Einträge im Archiv" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Keine Einträge im Archiv" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Ergebnissen der Suche sowie Autofill-Vorschlägen ausgeschlossen." }, "itemSentToArchive": { - "message": "Item sent to archive" + "message": "Eintrag an das Archiv gesendet" }, "itemRemovedFromArchive": { - "message": "Item removed from archive" + "message": "Eintrag aus dem Archiv entfernt" }, "archiveItem": { - "message": "Archive item" + "message": "Eintrag archivieren" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen und Autofill-Vorschlägen ausgeschlossen. Sind Sie sicher, dass Sie diesen Eintrag archivieren möchten?" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 0c2ee1fab65..3a0c5938e4b 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 625d8804676..ec95d939e0a 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 41211c2e7d7..7e07cbad38f 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index cebc2fa1432..fb9fe5cca9c 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 346dc0d4221..1dfd580f333 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden no valida las ubicaciones de entrada, asegúrate de que estás en la ventana y en el capo correctos antes de usar el atajo." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index b8afeb2ed6a..f4b5fdcc11a 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 7f719ec0a4b..173c3ece03e 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index fbbbdfd8c7f..9624647be95 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index ecde260d80e..3e2cfbc2650 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 5ad2661b46a..1ec8754bf96 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index f85708edbe0..fda65187812 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Afficher moins" }, - "enableAutotype": { - "message": "Activer la Saisie Auto" - }, "enableAutotypeDescription": { "message": "Bitwarden ne valide pas les emplacements d'entrée, assurez-vous d'être dans la bonne fenêtre et le bon champ avant d'utiliser le raccourci." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Plus de fil d'Ariane", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirmer" }, - "enableAutotypeTransitionKey": { - "message": "Activer le raccourci de la Saisie Auto" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Assurez-vous d'être dans le bon champ avant d'utiliser le raccourci pour éviter de remplir les données au mauvais endroit." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Modifier le raccourci" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Désarchiver" diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 5849d9d4cee..f301c30de7f 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 5cbceb3ad76..0b1e1b7ac9a 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "הצג פחות" }, - "enableAutotype": { - "message": "הפעל הקלדה אוטומטית" - }, "enableAutotypeDescription": { "message": "Bitwarden לא מאמת את מקומות הקלט, נא לוודא שזה החלון והשדה הנכונים בטרם שימוש בקיצור הדרך." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "עוד סימני דרך", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "אשר" }, - "enableAutotypeTransitionKey": { - "message": "הפעל קיצור דרך להקלדה אוטומטית" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "וודא שאתה נמצא בשדה הנכון לפני השימוש בקיצור הדרך כדי להימנע ממילוי נתונים במקום הלא נכון." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "ערוך קיצור דרך" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 25ecbdf3840..28c73a7b45f 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 64f89a8b15f..c3dc662949a 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Pokaži manje" }, - "enableAutotype": { - "message": "Omogući automatski unos" - }, "enableAutotypeDescription": { "message": "Bitwarden ne provjerava lokacije unosa, prije korištenja prečaca provjeri da si u pravom prozoru i polju." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Više mrvica", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Potvrdi" }, - "enableAutotypeTransitionKey": { - "message": "Omogući prečac za automatsko tipkanje" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Prije korištenja prečaca provjeri nalaziš li se u ispravnom polju kako se podaci ne bi unijeli na pogrešno mjesto." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Uredi prečac" }, - "archive": { - "message": "Arhiviraj" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Poništi arhiviranje" diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 9f71448ce5a..7f3d4572ea3 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Kevesebb megjelenítése" }, - "enableAutotype": { - "message": "Autotípus engedélyezése" - }, "enableAutotypeDescription": { "message": "A Bitwarden nem érvényesíti a beviteli helyeket, győződjünk meg róla, hogy a megfelelő ablakban és mezőben vagyunk, mielőtt a parancsikont használnánk." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "További morzsamenük", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Megerősítés" }, - "enableAutotypeTransitionKey": { - "message": "Automatikus típusú parancsikon engedélyezése" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Győződjünk meg arról, hogy a megfelelő mezőben vagyunk, mielőtt a parancsikont használnánk, hogy elkerüljük az adatok rossz helyre történő kitöltését." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Parancsikon szerkesztése" }, - "archive": { - "message": "Archívum" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Visszavétel archívumból" diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 3f44fd8bf97..acd61bf97ec 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 780d09f3582..09466c05807 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Mostra di meno" }, - "enableAutotype": { - "message": "Abilita auto immissione" - }, "enableAutotypeDescription": { "message": "Bitwarden non convalida i campi di input: assicurati di essere nella finestra e nel campo di testo corretti prima di usare la scorciatoia." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Ulteriori segmenti", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Conferma" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index a58543302fa..3231d7fa77f 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 0bb7e929979..4cfdea94e38 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 5849d9d4cee..f301c30de7f 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 66a6e43d0cb..608c427149a 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 59423b8ad73..8bbf4634a29 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 8159bc5e28b..152cdd03e86 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 5e29f10190b..2ccd255ea5b 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Rādīt mazāk" }, - "enableAutotype": { - "message": "Iespējot automātisko ievadi" - }, "enableAutotypeDescription": { "message": "Bitwarden nepārbauda ievades atrašanās vietas, jāpārliecinās, ka atrodies pareizajā logā un laukā, pirms saīsnes izmantošanas." }, + "typeShortcut": { + "message": "Ievadīt īsinājumtaustiņus" + }, + "editAutotypeShortcutDescription": { + "message": "Jāiekļauj viens vai divi no šiem taustiņiem - Ctrl, Alt, Win vai Shift - un burts." + }, + "invalidShortcut": { + "message": "Nederīgi īsinājumtaustiņi" + }, "moreBreadcrumbs": { "message": "Vairāk norāžu", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Apstiprināt" }, - "enableAutotypeTransitionKey": { - "message": "Iespējot automātiskās ievades saīsni" + "enableAutotypeShortcutPreview": { + "message": "Iespējot automātiskās ievades īsinājumtaustiņus (iespējas priekšskatījums)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Jāpārliecinās, ka pirms saīsnes izmantošanas kursors ir pareizajā laukā, lai izvairītos no datu ievadīšanas nepareizā vietā." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Labot saīsni" }, - "archive": { - "message": "Arhivēt" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Atcelt arhivēšanu" diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index b023d0efab0..6509e5aafc2 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 863f3941a0f..03ed49a8fea 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 5849d9d4cee..f301c30de7f 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index fae67d310f1..58562cfc522 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index ec1c1bdb9b5..97d3291875c 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 813fa967252..7c34e823553 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index c726c003776..719be29bf91 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Minder weergeven" }, - "enableAutotype": { - "message": "Autotypen inschakelen" - }, "enableAutotypeDescription": { "message": "Bitwarden valideert de invoerlocaties niet, zorg ervoor dat je je in het juiste venster en veld bevindt voordat je de snelkoppeling gebruikt." }, + "typeShortcut": { + "message": "Typ de sneltoets" + }, + "editAutotypeShortcutDescription": { + "message": "Voeg een of twee van de volgende toetsen toe: Ctrl, Alt, Win of Shift, en een letter." + }, + "invalidShortcut": { + "message": "Ongeldige sneltoets" + }, "moreBreadcrumbs": { "message": "Meer broodkruimels", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Bevestigen" }, - "enableAutotypeTransitionKey": { - "message": "Snelkoppeling autotype inschakelen" + "enableAutotypeShortcutPreview": { + "message": "Autotype sneltoets inschakelen (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Zorg ervoor dat je je in het juiste veld bevindt voordat je de snelkoppeling gebruikt om te voorkomen dat je de gegevens op de verkeerde plaats invult." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Snelkoppeling bewerken" }, - "archive": { - "message": "Archiveren" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Dearchiveren" diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 94c2196edfa..de0a57b0a79 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 217439bec80..2212adcf311 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 6caee67447f..d364ac819d9 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Pokaż mniej" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Więcej nawigacji", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Potwierdź" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edytuj skrót" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Usuń z archiwum" diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 4b28ca4918a..dc28cf47345 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Mostrar menos" }, - "enableAutotype": { - "message": "Habilitar Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden não valida localizações de entrada, tenha certeza de estar na janela e campo corretos antes de utilizar o atalho." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Mais trilhas", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 49573dcd647..5ba1c52f7a6 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Mostrar menos" }, - "enableAutotype": { - "message": "Ativar digitação automática" - }, "enableAutotypeDescription": { "message": "O Bitwarden não valida a introdução de localizações. Certifique-se de que está na janela e no campo corretos antes de utilizar o atalho." }, + "typeShortcut": { + "message": "Introduzir atalho" + }, + "editAutotypeShortcutDescription": { + "message": "Inclua um ou dois dos seguintes modificadores: Ctrl, Alt, Win, ou Shift, e uma letra." + }, + "invalidShortcut": { + "message": "Atalho inválido" + }, "moreBreadcrumbs": { "message": "Mais da navegação estrutural", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirmar" }, - "enableAutotypeTransitionKey": { - "message": "Ativar o atalho de introdução automática" + "enableAutotypeShortcutPreview": { + "message": "Ativar o atalho de digitação automática (Pré-visualização da funcionalidade)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Certifique-se de que está no campo correto antes de utilizar o atalho para evitar preencher dados no local errado." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Editar atalho" }, - "archive": { - "message": "Arquivar" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Desarquivar" diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 802afc3ef22..624cc8ef2c1 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 292564be5f3..54262cd9ec9 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Меньше" }, - "enableAutotype": { - "message": "Включить автоввод" - }, "enableAutotypeDescription": { "message": "Bitwarden не проверяет местоположение ввода, поэтому, прежде чем использовать ярлык, убедитесь, что вы находитесь в нужном окне и поле." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Больше хлебных крошек", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Подтвердить" }, - "enableAutotypeTransitionKey": { - "message": "Включить ярлык автоввода" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Прежде чем использовать ярлык, убедитесь, что вы поставили курсор в нужное поле, чтобы избежать ввода данных в неправильное место." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Изменить ярлык" }, - "archive": { - "message": "Архив" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Разархивировать" diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 8d4072e1da2..5b009f67f33 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 566b9b8210a..a37ff270fd9 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Zobraziť menej" }, - "enableAutotype": { - "message": "Povoliť automatické vpisovanie" - }, "enableAutotypeDescription": { "message": "Bitwarden neoveruje miesto stupu, pred použitím skratky sa uistite, že ste v správnom okne a poli." }, + "typeShortcut": { + "message": "Zadajte klávesovú skratku" + }, + "editAutotypeShortcutDescription": { + "message": "Použite jeden alebo dva z nasledujúcich modifikátorov: Ctrl, Alt, Win, alebo Shift a písmeno." + }, + "invalidShortcut": { + "message": "Neplatná klávesová skratka" + }, "moreBreadcrumbs": { "message": "Viac", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Potvrdiť" }, - "enableAutotypeTransitionKey": { - "message": "Povoliť skratku automatického písania" + "enableAutotypeShortcutPreview": { + "message": "Povoliť skratku pre automatické vpisovanie (náhľad funkcie)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Pred použitím skratky sa uistite, že sa nachádzate v správnom poli, aby ste údaje nevyplnili na nesprávne miesto." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Upraviť skratku" }, - "archive": { - "message": "Archivovať" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Zrušiť archiváciu" diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 23a6df7ad95..5141d046876 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index cd41aa9e4b9..5a4b0500183 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Прикажи мање" }, - "enableAutotype": { - "message": "Упали ауто-унос" - }, "enableAutotypeDescription": { "message": "Bitwarden не потврђује локације уноса, будите сигурни да сте у добром прозору и поље пре употребе пречице." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Више мрвица", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Потврди" }, - "enableAutotypeTransitionKey": { - "message": "Омогућава пречицу за аутоматски унос" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Будите сигурни да сте у исправном пољу пре употребе пречице да бисте избегли попуњавање података на погрешно место." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Уреди пречицу" }, - "archive": { - "message": "Архива" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Врати из архиве" diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index a867fc28753..26e97befd42 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Visa mindre" }, - "enableAutotype": { - "message": "Aktivera automatisk inmatning" - }, "enableAutotypeDescription": { "message": "Bitwarden validerar inte inmatningsplatser, så se till att du är i rätt fönster och fält innan du använder genvägen." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Inkludera en eller två av följande modifierare: Ctrl, Alt, Win, eller Skift och en bokstav." + }, + "invalidShortcut": { + "message": "Ogiltig genväg" + }, "moreBreadcrumbs": { "message": "Fler länkstigar", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Bekräfta" }, - "enableAutotypeTransitionKey": { - "message": "Aktivera genväg för automatisk inmatning" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Försäkra dig om att du befinner dig i rätt fält innan du använder genvägen för att undvika att fylla i data på fel ställe." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Redigera genväg" }, - "archive": { - "message": "Arkivera" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Packa upp" diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index 4874985a8fd..9fd0d75f2d1 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "குறைவாகக் காட்டு" }, - "enableAutotype": { - "message": "தானியங்கு வகையை இயக்கு" - }, "enableAutotypeDescription": { "message": "Bitwarden உள்ளீட்டு இடங்களைச் சரிபார்க்காது, ஷார்ட்கட்டைப் பயன்படுத்துவதற்கு முன் சரியான சாளரம் மற்றும் புலத்தில் நீங்கள் இருக்கிறீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "மேலும் பிரெட்க்ரம்புகள்", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "உறுதிப்படுத்து" }, - "enableAutotypeTransitionKey": { - "message": "தானியங்கு வகை குறுக்குவழியை இயக்கு" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "தவறான இடத்தில் தரவை நிரப்புவதைத் தவிர்க்க, குறுக்குவழியைப் பயன்படுத்துவதற்கு முன்பு நீங்கள் சரியான புலத்தில் இருப்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "குறுக்குவழியைத் திருத்து" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 5849d9d4cee..f301c30de7f 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index fa619695fdb..28d1a6bffcd 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index c33570af387..dd47f2662a3 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Daha az göster" }, - "enableAutotype": { - "message": "Otomatik yazmayı etkinleştir" - }, "enableAutotypeDescription": { "message": "Bitwarden giriş konumlarını doğrulamaz, kısayolu kullanmadan önce doğru pencerede ve alanda olduğunuzdan emin olun." }, + "typeShortcut": { + "message": "Kısayolu yazın" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Geçersiz kısayol" + }, "moreBreadcrumbs": { "message": "Daha fazla gezinme izi", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Onayla" }, - "enableAutotypeTransitionKey": { - "message": "Otomatik yazma kısayolunu etkinleştir" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Verilerin yanlış yere doldurulmasını önlemek için kısayolu kullanmadan önce doğru alanda olduğunuzdan emin olun." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Kısayolu düzenle" }, - "archive": { - "message": "Arşivle" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Arşivden çıkar" diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index c5c86baacdf..7f9e3a00fc3 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Згорнути" }, - "enableAutotype": { - "message": "Увімкнути автовведення" - }, "enableAutotypeDescription": { "message": "Bitwarden не перевіряє місця введення. Переконайтеся, що у вас відкрите правильне вікно і вибрано потрібне поле, перш ніж застосувати комбінацію клавіш." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Інші елементи", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index a9ac6aa5bd7..e255e69d55c 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -4080,12 +4080,18 @@ "showLess": { "message": "Thu gọn" }, - "enableAutotype": { - "message": "Bật tính năng Tự động nhập liệu" - }, "enableAutotypeDescription": { "message": "Bitwarden không kiểm tra vị trí nhập liệu, hãy đảm bảo bạn đang ở trong đúng cửa sổ và trường nhập liệu trước khi dùng phím tắt." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "Thêm mục điều hướng", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Xác nhận" }, - "enableAutotypeTransitionKey": { - "message": "Bật phím tắt tự động điền" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Hãy đảm bảo bạn đang ở đúng trường trước khi sử dụng phím tắt để tránh điền dữ liệu vào chỗ không đúng." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Chỉnh sửa phím tắt" }, - "archive": { - "message": "Lưu trữ" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Hủy lưu trữ" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 552afdca34c..ed7f93f3768 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -576,7 +576,7 @@ "message": "复制验证码 (TOTP)" }, "copyFieldCipherName": { - "message": "复制 $CIPHERNAME$ 的 $FIELD$", + "message": "复制 $CIPHERNAME$ 中的 $FIELD$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4080,12 +4080,18 @@ "showLess": { "message": "显示更少" }, - "enableAutotype": { - "message": "启用自动输入" - }, "enableAutotypeDescription": { "message": "Bitwarden 不会验证输入位置,在使用快捷键之前,请确保您位于正确的窗口和字段中。" }, + "typeShortcut": { + "message": "输入快捷键" + }, + "editAutotypeShortcutDescription": { + "message": "包括以下一个或两个修饰符:Ctrl、Alt、Win 或 Shift,外加一个字母。" + }, + "invalidShortcut": { + "message": "无效的快捷键" + }, "moreBreadcrumbs": { "message": "更多导航项", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "确认" }, - "enableAutotypeTransitionKey": { - "message": "启用自动输入快捷键" + "enableAutotypeShortcutPreview": { + "message": "启用自动输入快捷键(功能预览)" }, "enableAutotypeDescriptionTransitionKey": { "message": "在使用快捷键之前,请确保您位于正确的字段中,以避免将数据填入错误的地方。" @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "编辑快捷键" }, - "archive": { - "message": "归档" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "取消归档" diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index d4e579f89c1..9671957c9cc 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -3075,7 +3075,7 @@ "message": "Login request" }, "deviceType": { - "message": "裝置類別" + "message": "裝置類型" }, "ipAddress": { "message": "IP 位址" @@ -4080,12 +4080,18 @@ "showLess": { "message": "Show less" }, - "enableAutotype": { - "message": "Enable Autotype" - }, "enableAutotypeDescription": { "message": "Bitwarden does not validate input locations, be sure you are in the right window and field before using the shortcut." }, + "typeShortcut": { + "message": "Type shortcut" + }, + "editAutotypeShortcutDescription": { + "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + }, + "invalidShortcut": { + "message": "Invalid shortcut" + }, "moreBreadcrumbs": { "message": "More breadcrumbs", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." @@ -4099,8 +4105,8 @@ "confirm": { "message": "Confirm" }, - "enableAutotypeTransitionKey": { - "message": "Enable autotype shortcut" + "enableAutotypeShortcutPreview": { + "message": "Enable autotype shortcut (Feature Preview)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." @@ -4108,8 +4114,13 @@ "editShortcut": { "message": "Edit shortcut" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "unarchive": { "message": "Unarchive" From 91e90681c339f891178d16bbb522bede0d93e784 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 06:57:03 -0400 Subject: [PATCH 32/83] Autosync the updated translations (#16694) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 103 +++++-- apps/web/src/locales/ar/messages.json | 103 +++++-- apps/web/src/locales/az/messages.json | 103 +++++-- apps/web/src/locales/be/messages.json | 103 +++++-- apps/web/src/locales/bg/messages.json | 101 +++++-- apps/web/src/locales/bn/messages.json | 103 +++++-- apps/web/src/locales/bs/messages.json | 103 +++++-- apps/web/src/locales/ca/messages.json | 103 +++++-- apps/web/src/locales/cs/messages.json | 101 +++++-- apps/web/src/locales/cy/messages.json | 103 +++++-- apps/web/src/locales/da/messages.json | 103 +++++-- apps/web/src/locales/de/messages.json | 331 +++++++++++++---------- apps/web/src/locales/el/messages.json | 103 +++++-- apps/web/src/locales/en_GB/messages.json | 103 +++++-- apps/web/src/locales/en_IN/messages.json | 103 +++++-- apps/web/src/locales/eo/messages.json | 103 +++++-- apps/web/src/locales/es/messages.json | 103 +++++-- apps/web/src/locales/et/messages.json | 103 +++++-- apps/web/src/locales/eu/messages.json | 103 +++++-- apps/web/src/locales/fa/messages.json | 103 +++++-- apps/web/src/locales/fi/messages.json | 103 +++++-- apps/web/src/locales/fil/messages.json | 103 +++++-- apps/web/src/locales/fr/messages.json | 101 +++++-- apps/web/src/locales/gl/messages.json | 103 +++++-- apps/web/src/locales/he/messages.json | 103 +++++-- apps/web/src/locales/hi/messages.json | 103 +++++-- apps/web/src/locales/hr/messages.json | 107 ++++++-- apps/web/src/locales/hu/messages.json | 101 +++++-- apps/web/src/locales/id/messages.json | 103 +++++-- apps/web/src/locales/it/messages.json | 103 +++++-- apps/web/src/locales/ja/messages.json | 103 +++++-- apps/web/src/locales/ka/messages.json | 103 +++++-- apps/web/src/locales/km/messages.json | 103 +++++-- apps/web/src/locales/kn/messages.json | 103 +++++-- apps/web/src/locales/ko/messages.json | 103 +++++-- apps/web/src/locales/lv/messages.json | 103 +++++-- apps/web/src/locales/ml/messages.json | 103 +++++-- apps/web/src/locales/mr/messages.json | 103 +++++-- apps/web/src/locales/my/messages.json | 103 +++++-- apps/web/src/locales/nb/messages.json | 103 +++++-- apps/web/src/locales/ne/messages.json | 103 +++++-- apps/web/src/locales/nl/messages.json | 101 +++++-- apps/web/src/locales/nn/messages.json | 103 +++++-- apps/web/src/locales/or/messages.json | 103 +++++-- apps/web/src/locales/pl/messages.json | 103 +++++-- apps/web/src/locales/pt_BR/messages.json | 103 +++++-- apps/web/src/locales/pt_PT/messages.json | 101 +++++-- apps/web/src/locales/ro/messages.json | 103 +++++-- apps/web/src/locales/ru/messages.json | 103 +++++-- apps/web/src/locales/si/messages.json | 103 +++++-- apps/web/src/locales/sk/messages.json | 103 +++++-- apps/web/src/locales/sl/messages.json | 103 +++++-- apps/web/src/locales/sr_CS/messages.json | 103 +++++-- apps/web/src/locales/sr_CY/messages.json | 103 +++++-- apps/web/src/locales/sv/messages.json | 171 ++++++++---- apps/web/src/locales/ta/messages.json | 103 +++++-- apps/web/src/locales/te/messages.json | 103 +++++-- apps/web/src/locales/th/messages.json | 103 +++++-- apps/web/src/locales/tr/messages.json | 103 +++++-- apps/web/src/locales/uk/messages.json | 103 +++++-- apps/web/src/locales/vi/messages.json | 103 +++++-- apps/web/src/locales/zh_CN/messages.json | 105 +++++-- apps/web/src/locales/zh_TW/messages.json | 103 +++++-- 63 files changed, 5122 insertions(+), 1657 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index fcfd90269f2..5690392f205 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Daar is geen items om te lys nie." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisasie is gedeaktiveer." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "Lisensie het verstryk." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO-identifiseerder" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Groep/Gebruiker" }, - "lowKdfIterations": { - "message": "Lae KDF-iteraties" - }, - "updateLowKdfIterationsDesc": { - "message": "Werk u enkripsie-instellings by om aan die nuwe beveiligingsaanbevelings te voldoen en die beskerming van u rekening te verbeter." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Verander KDF-instellings" - }, "secureYourInfrastructure": { "message": "Beveilig u infrastruktuur" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index fd0214fa71d..98b84791237 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "إنشاء عنصر تسجيل دخول جديد" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "التطبيقات الحساسة ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "الأعضاء المبلّغون ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "لا توجد أية عناصر في القائمة." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "ليس لديك الصلاحية لعرض جميع العناصر في هذه المجموعة." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "تم تعليق المؤسسة" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "معرف SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index c9d53bd9c52..7b9bbe01c62 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Yeni giriş elementi yarat" }, - "criticalApplicationsActivityDescription": { - "message": "Tətbiqləri kritik olaraq işarələsəniz, onlar burada nümayiş olunacaq." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritik tətbiqlər ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Məlumatlandırılan üzvlər ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Kritik tətbiqlər üçün risk altındakı elementlərə düzəliş erişimi olan üzvlər" }, - "membersAtRisk": { - "message": "$COUNT$ üzv riskdədir", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Sadalanacaq heç bir element yoxdur." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Bu kolleksiyadakı bütün elementlərə baxmaq üçün icazəniz yoxdur." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Təşkilat sıradan çıxarıldı." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Fəaliyyəti dayandırılmış təşkilatlara erişilə bilməz. Lütfən kömək üçün təşkilatınızın sahibi ilə əlaqə saxlayın." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Xidmət hesabları dayandırılmış təşkilatlarda yaradıla bilməz. Lütfən kömək üçün təşkilatınızın sahibi ilə əlaqə saxlayın." }, - "disabledOrganizationFilterError": { - "message": "Fəaliyyəti dayandırılmış təşkilatlardakı elementlərə erişilə bilməz. Kömək üçün təşkilatınızın sahibi ilə əlaqə saxlayın." - }, "licenseIsExpired": { "message": "Lisenziyanın vaxtı bitib." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifikatoru" }, - "ssoIdentifierHintPartOne": { - "message": "SSO ilə giriş etmələri üçün üzvlərinizi bu kimliklə təmin edin. Bu addımı ötürmək üçün, quraşdırın ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO əlaqəsini kəs" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Qrup/İstifadəçi" }, - "lowKdfIterations": { - "message": "Aşağı KDF iterasiyaları" - }, - "updateLowKdfIterationsDesc": { - "message": "Yeni güvənlik tövsiyələrini qarşılamaq və hesab qorumasını təkmilləşdirmək üçün şifrələmə ayarlarınızı güncəlləyin." - }, "kdfSettingsChangeLogoutWarning": { "message": "Davam etsəniz, bütün aktiv seanslardan çıxış edəcəksiniz. Təkrar giriş etməyiniz və əgər varsa iki addımlı girişi tamamlamağınız lazım olacaq. Veri itkisini önləmək üçün şifrələmə ayarlarınızı dəyişdirməzdən əvvəl seyfinizi xaricə köçürməyinizi tövsiyə edirik." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "İnteqrasiya saxlanılmadı. Lütfən daha sonra yenidən sınayın." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "İnteqrasiya silinmədi. Lütfən daha sonra yenidən sınayın." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Aşağı KDF iterasiyaları. Hesabınızın güvənliyini təkmilləşdirmək üçün iterasiyaları artırın." - }, - "changeKDFSettings": { - "message": "KDF ayarlarını dəyişdir" - }, "secureYourInfrastructure": { "message": "İnfrastrukturunuzu qoruyun" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Arxivdə axtar" }, - "archive": { - "message": "Arxiv" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Arxivdə element yoxdur" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 91eb4328810..e1f5639cda4 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Стварыць новы элемент запісу ўваходу" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Крытычныя праграмы ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Апавешчаныя ўдзельнікі ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "У спісе адсутнічаюць элементы." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "У вас няма правоў для прагляду ўсіх элементаў у гэтай калекцыі." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Арганізацыя адключана." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Доступ да элементаў у адключаных арганізацыях немагчымы. Звяжыце з уладальнікам арганізацыі для атрымання дапамогі." - }, "licenseIsExpired": { "message": "Ліцэнзія пратэрмінавана." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Ідэнтыфікатар SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Адлучыць SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Група/Карыстальнік" }, - "lowKdfIterations": { - "message": "Нізкае значэнне ітэрацыі KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Абнавіце свае налады шыфравання, каб адпавядаць новым рэкамендацыям бяспекі і палепшыць абарону ўліковага запісу." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 86136cb8f16..19119a31c34 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Създаване на нов елемент за вписване" }, - "criticalApplicationsActivityDescription": { - "message": "Когато отбележите някое приложение като важно, то ще се появи тук." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Когато отбележите някое приложение като важно, то ще се появи тук" + }, + "viewAtRiskMembers": { + "message": "Преглед на членовете в риск" + }, + "viewAtRiskApplications": { + "message": "Преглед на приложенията в риск" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ от $TOTAL$ важни приложения са в риск, тъй като има пароли в риск", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Важни приложения ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ прилжения са в риск", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Известени членове ($COUNT$)", "placeholders": { @@ -137,7 +165,7 @@ "membersAtRiskActivityDescription": { "message": "Членове с права за редактиране на елементи в риск за важни приложения" }, - "membersAtRisk": { + "membersAtRiskCount": { "message": "$COUNT$ членове в риск", "placeholders": { "count": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Няма елементи за показване." }, + "noItemsInTrash": { + "message": "Няма елементи в кошчето" + }, + "noItemsInTrashDesc": { + "message": "Елементите, които изтривате, ще бъдат премествани тук и изтривани окончателно след 30 дни" + }, + "noItemsInVault": { + "message": "Няма елементи в трезора" + }, + "emptyVaultDescription": { + "message": "Трезорът може да пази не само паролите Ви. Тум можете да съхранявате сигурно данни за вписване, идентификационни данни, карти и бележки." + }, + "emptyFavorites": { + "message": "Все още нямате любими неща" + }, + "emptyFavoritesDesc": { + "message": "Добавете често ползваните неща в любимите си, за бърз достъп." + }, + "noSearchResults": { + "message": "Няма резултати от търсенето" + }, + "clearFiltersOrTryAnother": { + "message": "Изчистете филтрите или опитайте да търсите нещо друго" + }, "noPermissionToViewAllCollectionItems": { "message": "Нямате права да преглеждате всички елементи в тази колекция." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Организацията е изключена." }, + "organizationIsSuspended": { + "message": "Организацията е с преустановен достъп" + }, + "organizationIsSuspendedDesc": { + "message": "Записите в организации с преустановен достъп не са достъпни. Свържете се със собственика на организацията си за помощ." + }, "secretsAccessSuspended": { "message": "Изключените организации са недостъпни. Свържете се със собственика на организацията си за помощ." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "В изключени организации не могат да се създават сервизни акаунти. Свържете се със собственика на организацията си за помощ." }, - "disabledOrganizationFilterError": { - "message": "Записите в изключени организации не са достъпни. Свържете се със собственика на организацията си за помощ." - }, "licenseIsExpired": { "message": "Изтекъл лиценз." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Идентификатор за еднократна идентификация" }, - "ssoIdentifierHintPartOne": { - "message": "Дайте този идентификатор на членовете си, за да могат да се вписват чрез еднократно удостоверяване. Ако искате да пропуснете тази стъпка, настройте ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Дайте този идентификатор на членовете си, за да се вписват чрез еднократно удостоверяване. Членовете могат да пропуснат въвеждането та този идентификатор по време на еднократното удостоверяване, ако е настроен присвоен домейн. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Научете повече", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Прекъсване на еднократна идентификация" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Група/потребител" }, - "lowKdfIterations": { - "message": "Малко повторения за KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Променете настройките си за шифроване, така че да отговарят на новите препоръки за сигурността и да подобрите защитата на регистрацията си." - }, "kdfSettingsChangeLogoutWarning": { "message": "Ако продължите, ще излезете от всички активни сесии. Ще трябва да се впишете отново и да извършите двустепенно удостоверяване, ако такова е настроено. Препоръчително е да изнесете трезора си преди да променяте настройките за шифроване, за да избегнете загуба на данни." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Интеграцията не беше запазена. Опитайте отново по-късно." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Трябва да бъдете собственик на организацията, за да извършите това действие." + }, "failedToDeleteIntegration": { "message": "Интеграцията не беше изтрита. Опитайте отново по-късно." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Малък брой повторения за KDF. Увеличете броя повторения, за да подсилите защитата на регистрацията си." - }, - "changeKDFSettings": { - "message": "Промяна на настройките за KDF" - }, "secureYourInfrastructure": { "message": "Подсигурете инфраструктурата си" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Търсене в архива" }, - "archive": { - "message": "Архив" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Няма елементи в архива" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 53f4cc91ae1..e5d2671bb9e 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "তালিকার জন্য কোনও বস্তু নেই।" }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index f53fc830b7a..26835283fc2 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index d9dfa7c1eac..8dc3825e8c5 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Crea un nou element d'inici de sessió" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Aplicacions crítiques ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membres notificats ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "No hi ha cap element a llistar." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "No teniu permís per veure tots els elements en aquesta col·lecció." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "L'organització està inhabilitada." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "No es pot accedir a les organitzacions suspeses. Poseu-vos en contacte amb el propietari de la vostra organització per obtenir ajuda." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "No es poden crear comptes de servei en organitzacions suspeses. Poseu-vos en contacte amb el propietari de la vostra organització per obtenir ajuda." }, - "disabledOrganizationFilterError": { - "message": "No es pot accedir als elements de les organitzacions inhabilitades. Poseu-vos en contacte amb el propietari de la vostra organització per obtenir ajuda." - }, "licenseIsExpired": { "message": "Llicència caducada." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Identificador SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Proporcioneu aquest identificador als vostres membres per iniciar sessió amb SSO. Per evitar aquest pas, configureu-ho ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Desenllaça SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grup/Usuari" }, - "lowKdfIterations": { - "message": "Iteracions de KDF baixes" - }, - "updateLowKdfIterationsDesc": { - "message": "Actualitzeu la configuració d'encriptació per complir les noves recomanacions de seguretat i millorar la protecció del compte." - }, "kdfSettingsChangeLogoutWarning": { "message": "Si continueu, tancareu totes les sessions actives. Haureu de tornar a iniciar la sessió i completar l'inici de sessió en dos passos. Recomanem que exporteu la caixa forta abans de canviar la configuració de xifratge per evitar la pèrdua de dades." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Iteracions baixes de KDF. Augmenta les iteracions per millorar la seguretat del teu compte." - }, - "changeKDFSettings": { - "message": "Canvia la configuració de KDF" - }, "secureYourInfrastructure": { "message": "Assegureu la vostra infraestructura" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 5837d159e92..2fd97aab27e 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Vytvořit novou položku přihlášení" }, - "criticalApplicationsActivityDescription": { - "message": "Jakmile označíte aplikace jako kritické, zobrazí se zde." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Jakmile označíte aplikace jako kritické, zobrazí se zde" + }, + "viewAtRiskMembers": { + "message": "Zobrazit ohrožené členy" + }, + "viewAtRiskApplications": { + "message": "Zobrazit ohrožené aplikace" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ z $TOTAL$ kritických aplikací je ohrožených kvůli ohroženým heslům", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritické aplikace ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ ohrožených aplikací", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Obeznámení členové ($COUNT$)", "placeholders": { @@ -137,7 +165,7 @@ "membersAtRiskActivityDescription": { "message": "Členové, kteří upravují přístup k rizikovým položkám pro kritické aplikace" }, - "membersAtRisk": { + "membersAtRiskCount": { "message": "$COUNT$ členů v ohrožení", "placeholders": { "count": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Žádné položky k zobrazení." }, + "noItemsInTrash": { + "message": "Žádné položky v koši" + }, + "noItemsInTrashDesc": { + "message": "Položky, které smažete, se zde zobrazí a budou trvale smazány po 30 dnech" + }, + "noItemsInVault": { + "message": "Žádné položky v trezoru" + }, + "emptyVaultDescription": { + "message": "Trezor chrání více než jen Vaše hesla. Bezpečně zde uložte zabezpečená přihlášení, ID, karty a poznámky." + }, + "emptyFavorites": { + "message": "Nemáte oblíbené žádné položky" + }, + "emptyFavoritesDesc": { + "message": "Přidejte často používané položky do oblíbených pro rychlý přístup." + }, + "noSearchResults": { + "message": "Nebyly vráceny žádné výsledky hledání" + }, + "clearFiltersOrTryAnother": { + "message": "Vymažte filtry nebo zkuste jiný hledaný výraz" + }, "noPermissionToViewAllCollectionItems": { "message": "Nemáte oprávnění k zobrazení všech položek v této sbírce." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organizace byla deaktivována" }, + "organizationIsSuspended": { + "message": "Organizace je deaktivována" + }, + "organizationIsSuspendedDesc": { + "message": "K položkám v deaktivované organizaci nemáte přístup. Požádejte o pomoc vlastníka organizace." + }, "secretsAccessSuspended": { "message": "K deaktivované organizaci nemáte přístup. Požádejte o pomoc vlastníka organizace." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Servisní účty nelze vytvořit v pozastavených organizacích. Požádejte o pomoc vlastníka organizace." }, - "disabledOrganizationFilterError": { - "message": "K položkám v deaktivované organizaci nemáte přístup. Požádejte o pomoc vlastníka organizace." - }, "licenseIsExpired": { "message": "Licence vypršela." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifikátor" }, - "ssoIdentifierHintPartOne": { - "message": "Poskytněte toto ID Vašim členům pro přihlášení pomocí SSO. Chcete-li obejít tento krok, nastavte ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Zadejte toto ID svým členům a přihlaste se pomocí SSO. Členové mohou přeskočit zadání tohoto identifikátoru během SSO, pokud je nastavena požadovaná doména. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Dozvědět se více", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Zrušit propojení s SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Skupina/Uživatel" }, - "lowKdfIterations": { - "message": "Nízke iterace KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Aktualizujte své nastavení šifrování tak, aby splňovalo nová bezpečnostní doporučení a zlepšilo ochranu účtu." - }, "kdfSettingsChangeLogoutWarning": { "message": "Pokračováním se odhlásíte ze všech aktivních relací. Budete se muset znovu přihlásit a dokončit nastavení dvoufázového přihlášení, pokud nějaké máte. Před změnou nastavení šifrování doporučujeme exportovat trezor, aby nedošlo ke ztrátě dat." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Nepodařilo se uložit integraci. Opakujte akci později." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Pro provedení této akce musíte být vlastníkem organizace." + }, "failedToDeleteIntegration": { "message": "Nepodařilo se smazat integraci. Opakujte akci později." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Nízké iterace KDF. Zvyšte iterace pro zvýšení bezpečnosti Vašeho účtu." - }, - "changeKDFSettings": { - "message": "Změnit nastavení KDF" - }, "secureYourInfrastructure": { "message": "Zabezpečte Vaši infrastrukturu" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Hledat v archivu" }, - "archive": { - "message": "Archivovat" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Žádné položky v archivu" diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 43fc75230a9..5fba67468aa 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index f5441544b96..00e6f8c2389 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Opret nyt login-emne" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritiske apps ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Underrettede medlemmer ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Der er ingen emner at vise." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Ingen tilladelse til at se alle emner i denne samling." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisation suspenderet" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspenderede organisationer kan ikke tilgås. Kontakt organisationsejeren for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Tjenestekonti kan ikke oprettes i suspenderede organisationer. Kontakt organisationsejeren for hjælp." }, - "disabledOrganizationFilterError": { - "message": "Emner i deaktiverede organisationer kan ikke tilgås. Kontakt oganisationsejeren for hjælp." - }, "licenseIsExpired": { "message": "Licensen er udløbet." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO-identifikator" }, - "ssoIdentifierHintPartOne": { - "message": "Giv medlemmerne dette ID til indlogning med SSO. For at omgå dette trin, opsæt ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Fjern SSO-tilknytning" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Gruppe/Bruger" }, - "lowKdfIterations": { - "message": "Få KDF-iterationer" - }, - "updateLowKdfIterationsDesc": { - "message": "Opdatér krypteringsindstillingerne for at opfylde nye sikkerhedsanbefalinger og forbedre kontobeskyttelsen." - }, "kdfSettingsChangeLogoutWarning": { "message": "Fortsættes, logges alle aktive sessioner af. Man vil skulle logge ind igen og færdiggøre opsætningen af totrinsindlogning. Eksport af boksen inden krypteringsindstillingerne ændres anbefales for at forhindre datatab." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Få KDF-iterationer. Forøg iterationer for at forbedre kontosikkerheden." - }, - "changeKDFSettings": { - "message": "Skift KDF-indstillinger" - }, "secureYourInfrastructure": { "message": "Sikr infrastrukturen" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 012f562fe8c..8019ed6950b 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -3,7 +3,7 @@ "message": "Alle Anwendungen" }, "activity": { - "message": "Activity" + "message": "Aktivität" }, "appLogoLabel": { "message": "Bitwarden-Logo" @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Neuen Zugangsdaten-Eintrag erstellen" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Sobald du Anwendungen als kritisch markierst, werden sie hier angezeigt" + }, + "viewAtRiskMembers": { + "message": "Gefährdete Mitglieder anzeigen" + }, + "viewAtRiskApplications": { + "message": "Gefährdete Anwendungen anzeigen" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ von $TOTAL$ kritischen Anwendungen sind aufgrund gefährdeter Passwörter gefährdet", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritische Anwendungen ($COUNT$)", @@ -72,7 +91,16 @@ } }, "countOfCriticalApplications": { - "message": "$COUNT$ critical applications", + "message": "$COUNT$ kritische Anwendungen", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ gefährdete Anwendungen", "placeholders": { "count": { "content": "$1", @@ -102,13 +130,13 @@ "message": "Während Benutzer Zugangsdaten speichern, werden hier Anwendungen angezeigt, die alle gefährdeten Passwörter anzeigen. Markiere kritische Anwendungen und benachrichtige Benutzer, um Passwörter zu ändern." }, "noCriticalApplicationsTitle": { - "message": "You haven’t marked any applications as critical" + "message": "Du hast keine Anwendung als kritisch markiert" }, "noCriticalApplicationsDescription": { - "message": "Select your most critical applications to prioritize security actions for your users to address at-risk passwords." + "message": "Wähle deine kritischsten Anwendungen aus, um Sicherheitsmaßnahmen für deine Benutzer zu priorisieren und gefährdete Passwörter zu beheben." }, "markCriticalApplications": { - "message": "Select critical applications" + "message": "Kritische Anwendungen auswählen" }, "markAppAsCritical": { "message": "Anwendung als kritisch markieren" @@ -135,10 +163,10 @@ "message": "Gefährdete Mitglieder" }, "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "message": "Mitglieder mit Berechtigung zum Bearbeiten gefährdeter Einträge für kritische Anwendungen" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ gefährdete Mitglieder", "placeholders": { "count": { "content": "$1", @@ -204,10 +232,10 @@ "message": "Anwendungen insgesamt" }, "unmarkAsCritical": { - "message": "Unmark as critical" + "message": "Als kritisch-Markierung aufheben" }, "criticalApplicationUnmarkedSuccessfully": { - "message": "Successfully unmarked application as critical" + "message": "Anwendung erfolgreich als kritisch-Markierung aufgehoben" }, "whatTypeOfItem": { "message": "Um welche Art von Eintrag handelt es sich hierbei?" @@ -762,79 +790,79 @@ "message": "Eintrag anzeigen" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "Neue Zugangsdaten", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "Neue Karte", "description": "Header for new card item type" }, "newItemHeaderIdentity": { - "message": "New Identity", + "message": "Neue Identität", "description": "Header for new identity item type" }, "newItemHeaderNote": { - "message": "New Note", + "message": "Neue Notiz", "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "Neuer SSH-Schlüssel", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "Neuen Text senden", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "Neue Datei senden", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "Zugangsdaten bearbeiten", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "Karte bearbeiten", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { - "message": "Edit Identity", + "message": "Identität bearbeiten", "description": "Header for edit identity item type" }, "editItemHeaderNote": { - "message": "Edit Note", + "message": "Notiz bearbeiten", "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "SSH-Schlüssel bearbeiten", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "Text bearbeiten Send", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "Datei bearbeiten Send", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "Zugangsdaten anzeigen", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "Karte anzeigen", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "View Identity", + "message": "Identität anzeigen", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "View Note", + "message": "Notiz anzeigen", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "SSH-Schlüssel anzeigen", "description": "Header for view SSH key item type" }, "new": { @@ -944,13 +972,13 @@ "message": "Lizenznummer kopieren" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Privaten Schlüssel kopieren" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Öffentlichen Schlüssel kopieren" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Fingerabdruck kopieren" }, "copyName": { "message": "Name kopieren" @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Keine Einträge vorhanden." }, + "noItemsInTrash": { + "message": "Keine Einträge im Papierkorb" + }, + "noItemsInTrashDesc": { + "message": "Einträge, die du löschst, erscheinen hier und werden nach 30 Tagen dauerhaft gelöscht" + }, + "noItemsInVault": { + "message": "Keine Einträge im Tresor" + }, + "emptyVaultDescription": { + "message": "Der Tresor schützt mehr als nur Ihre Passwörter. Speichern Sie hier sichere Zugangsdaten, Ausweise, Karten und Notizen." + }, + "emptyFavorites": { + "message": "Du hast keine Einträge zu Favoriten hinzugefügt" + }, + "emptyFavoritesDesc": { + "message": "Füge häufig verwendete Einträge zu Favoriten hinzu, um schnell darauf zuzugreifen." + }, + "noSearchResults": { + "message": "Keine Suchergebnisse gefunden" + }, + "clearFiltersOrTryAnother": { + "message": "Filter löschen oder einen anderen Suchbegriff versuchen" + }, "noPermissionToViewAllCollectionItems": { "message": "Du hast nicht die Berechtigung, alle Einträge in dieser Sammlung anzuzeigen." }, @@ -1590,7 +1642,7 @@ "message": "Wiederherstellungscode" }, "invalidRecoveryCode": { - "message": "Invalid recovery code" + "message": "Ungültiger Wiederherstellungscode" }, "authenticatorAppTitle": { "message": "Authenticator App" @@ -2136,10 +2188,10 @@ "message": "Sammlung auswählen" }, "importTargetHintCollection": { - "message": "Select this option if you want the imported file contents moved to a collection" + "message": "Wähle diese Option, wenn die Inhalte der Import-Datei in eine Sammlung verschoben werden sollen" }, "importTargetHintFolder": { - "message": "Select this option if you want the imported file contents moved to a folder" + "message": "Wähle diese Option, wenn die Inhalte der Import-Datei in einen Ordner verschoben werden sollen" }, "importUnassignedItemsError": { "message": "Die Datei enthält nicht zugewiesene Einträge." @@ -2188,7 +2240,7 @@ "message": "Die Sprache des Web-Tresors ändern." }, "showIconsChangePasswordUrls": { - "message": "Show website icons and retrieve change password URLs" + "message": "Website-Symbole anzeigen und URLs zum Ändern von Passwörtern abrufen" }, "default": { "message": "Standard" @@ -2900,10 +2952,10 @@ } }, "showPricingSummary": { - "message": "Show pricing summary" + "message": "Preisübersicht anzeigen" }, "hidePricingSummary": { - "message": "Hide pricing summary" + "message": "Preisübersicht ausblenden" }, "summary": { "message": "Zusammenfassung" @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisation ist deaktiviert" }, + "organizationIsSuspended": { + "message": "Organisation ist gesperrt" + }, + "organizationIsSuspendedDesc": { + "message": "Auf Einträge in gesperrten Organisationen kann nicht zugegriffen werden. Wenden Sie sich an den Eigentümer Ihrer Organisation, um Unterstützung zu erhalten." + }, "secretsAccessSuspended": { "message": "Auf deaktivierte Organisationen kann nicht zugegriffen werden. Bitte wende dich an deinen Organisationseigentümer." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Dienstkonten können in deaktivierten Organisationen nicht erstellt werden. Bitte kontaktiere deinen Organisationseigentümer, um Unterstützung zu erhalten." }, - "disabledOrganizationFilterError": { - "message": "Auf Einträge in deaktivierten Organisationen kann nicht zugegriffen werden. Kontaktiere deinen Organisationseigentümer für Unterstützung." - }, "licenseIsExpired": { "message": "Lizenz ist abgelaufen." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO-Kennung" }, - "ssoIdentifierHintPartOne": { - "message": "Gib diese ID deinen Mitgliedern zur Anmeldung über SSO. Um diesen Schritt zu umgehen, aktiviere die ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO-Verknüpfung aufheben" @@ -5558,10 +5617,10 @@ "message": "Aufgrund einer Unternehmensrichtlinie darfst du keine Einträge in deinem persönlichen Tresor speichern. Ändere die Eigentümer-Option in eine Organisation und wähle aus den verfügbaren Sammlungen." }, "desktopAutotypePolicy": { - "message": "Desktop Autotype Default Setting" + "message": "Desktop-Autotype-Standard­einstellung" }, "desktopAutotypePolicyDesc": { - "message": "Turn Desktop Autotype ON by default for members. Members can turn Autotype off manually in the Desktop client.", + "message": "Aktivieren Sie Desktop-Autotype standardmäßig für Mitglieder. Mitglieder können Autotype im Desktop-Client manuell deaktivieren.", "description": "This policy will enable Desktop Autotype by default for members on Unlock." }, "disableSend": { @@ -6729,7 +6788,7 @@ "message": "SSO deaktiviert" }, "emailMustLoginWithSso": { - "message": "$EMAIL$ must login with Single Sign-on", + "message": "$EMAIL$ muss sich mit Single Sign-on anmelden", "placeholders": { "email": { "content": "$1", @@ -7127,10 +7186,10 @@ "message": "Unbekannter Eintrag. Du musst möglicherweise eine Berechtigung anfordern, um auf diesen Eintrag zuzugreifen." }, "unknownSecret": { - "message": "Unknown secret, you may need to request permission to access this secret." + "message": "Unbekanntes Geheimnis, möglicherweise müssen Sie eine Berechtigung anfordern, um auf dieses Geheimnis zuzugreifen." }, "unknownProject": { - "message": "Unknown project, you may need to request permission to access this project." + "message": "Unbekanntes Projekt, möglicherweise müssen Sie eine Berechtigung anfordern, um auf dieses Projekt zuzugreifen." }, "cannotSponsorSelf": { "message": "Du kannst nicht für das aktive Konto einlösen. Gib eine andere E-Mail ein." @@ -7520,7 +7579,7 @@ "message": "Aus" }, "connected": { - "message": "Connected" + "message": "Verbunden" }, "members": { "message": "Mitglieder" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Gruppe/Benutzer" }, - "lowKdfIterations": { - "message": "Geringe KDF-Iterationen" - }, - "updateLowKdfIterationsDesc": { - "message": "Aktualisiere deine Verschlüsselungseinstellungen, um neue Sicherheitsempfehlungen zu erfüllen und den Kontoschutz zu verbessern." - }, "kdfSettingsChangeLogoutWarning": { "message": "Wenn du fortfährst, wirst du von allen aktiven Sitzungen abgemeldet. Du musst dich erneut anmelden und, falls eingerichtet, die Zwei-Faktor-Authentifizierung durchführen. Wir empfehlen, deinen Tresor zu exportieren, bevor du deine Verschlüsselungseinstellungen änderst, um Datenverluste zu vermeiden." }, @@ -8459,7 +8512,7 @@ } }, "permanentlyDeletedSecretWithId": { - "message": "Permanently deleted a secret with identifier: $SECRET_ID$", + "message": "Ein Geheimnis mit der Kennung $SECRET_ID$ wurde dauerhaft gelöscht", "placeholders": { "secret_id": { "content": "$1", @@ -8468,7 +8521,7 @@ } }, "restoredSecretWithId": { - "message": "Restored a secret with identifier: $SECRET_ID$", + "message": "Ein Geheimnis mit der Kennung $SECRET_ID$ wurde wiederhergestellt", "placeholders": { "secret_id": { "content": "$1", @@ -8486,7 +8539,7 @@ } }, "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "message": "Auf ein Projekt mit der ID $PROJECT_ID$ zugegriffen.", "placeholders": { "project_id": { "content": "$1", @@ -8495,7 +8548,7 @@ } }, "nameUnavailableProjectDeleted": { - "message": "Deleted project Id: $PROJECT_ID$", + "message": "Projekt mit der ID $PROJECT_ID$ gelöscht", "placeholders": { "project_id": { "content": "$1", @@ -8504,7 +8557,7 @@ } }, "nameUnavailableSecretDeleted": { - "message": "Deleted secret Id: $SECRET_ID$", + "message": "Geheimnis mit der ID $SECRET_ID$ gelöscht", "placeholders": { "secret_id": { "content": "$1", @@ -8513,7 +8566,7 @@ } }, "editedProjectWithId": { - "message": "Edited a project with identifier: $PROJECT_ID$", + "message": "Ein Projekt mit der Kennung $PROJECT_ID$ bearbeitet", "placeholders": { "project_id": { "content": "$1", @@ -8522,7 +8575,7 @@ } }, "deletedProjectWithId": { - "message": "Deleted a project with identifier: $PROJECT_ID$", + "message": "Ein Projekt mit der Kennung $PROJECT_ID$ gelöscht", "placeholders": { "project_id": { "content": "$1", @@ -8531,7 +8584,7 @@ } }, "createdProjectWithId": { - "message": "Created a new project with identifier: $PROJECT_ID$", + "message": "Ein neues Projekt mit der Kennung $PROJECT_ID$ erstellt", "placeholders": { "project_id": { "content": "$1", @@ -9028,23 +9081,23 @@ "message": "Verwaltung der Sammlungen" }, "collectionManagementDescription": { - "message": "Configure the collection behavior for the organization" + "message": "Konfigurieren Sie das Sammlungsverhalten für die Organisation" }, "allowAdminAccessToAllCollectionItemsDescription": { - "message": "Allow owners and admins to manage all collections and items from the Admin Console" + "message": "Erlauben Sie Eigentümern und Administratoren, alle Sammlungen und Einträge über die Admin-Konsole zu verwalten" }, "restrictCollectionCreationDescription": { - "message": "Restrict collection creation to owners and admins" + "message": "Beschränken Sie die Erstellung von Sammlungen auf Eigentümer und Administratoren" }, "restrictCollectionDeletionDescription": { - "message": "Restrict collection deletion to owners and admins" + "message": "Beschränken Sie das Löschen von Sammlungen auf Eigentümer und Administratoren" }, "restrictItemDeletionDescriptionStart": { - "message": "Restrict item deletion to members with the ", + "message": "Beschränken Sie das Löschen von Einträgen auf Mitglieder mit der ", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "restrictItemDeletionDescriptionEnd": { - "message": " permission", + "message": " Berechtigung", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "updatedCollectionManagement": { @@ -9124,7 +9177,7 @@ } }, "limitCollectionCreationEnabled": { - "message": "Turned on Restrict collection creation setting $ID$.", + "message": "Die Einstellung Sammlungserstellung einschränken wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9133,7 +9186,7 @@ } }, "limitCollectionCreationDisabled": { - "message": "Turned off Restrict collection creation setting $ID$.", + "message": "Die Einstellung Sammlungserstellung einschränken wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9142,7 +9195,7 @@ } }, "limitCollectionDeletionEnabled": { - "message": "Turned on Restrict collection deletion setting $ID$.", + "message": "Die Einstellung Sammlungslöschung einschränken wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9151,7 +9204,7 @@ } }, "limitCollectionDeletionDisabled": { - "message": "Turned off Restrict collection deletion setting $ID$.", + "message": "Die Einstellung Sammlungslöschung einschränken wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9160,7 +9213,7 @@ } }, "limitItemDeletionEnabled": { - "message": "Turned on Restrict item deletion setting $ID$.", + "message": "Die Einstellung Eintragslöschung einschränken wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9169,7 +9222,7 @@ } }, "limitItemDeletionDisabled": { - "message": "Turned off Restrict item deletion setting $ID$.", + "message": "Die Einstellung Eintragslöschung einschränken wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9178,7 +9231,7 @@ } }, "allowAdminAccessToAllCollectionItemsEnabled": { - "message": "Turned on Allow owners and admins to manage all collections and items setting $ID$.", + "message": "Die Einstellung Eigentümern und Administratoren erlauben, alle Sammlungen und Einträge zu verwalten wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9187,7 +9240,7 @@ } }, "allowAdminAccessToAllCollectionItemsDisabled": { - "message": "Turned off Allow owners and admins to manage all collections and items setting $ID$.", + "message": "Die Einstellung Eigentümern und Administratoren erlauben, alle Sammlungen und Einträge zu verwalten wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9755,8 +9808,11 @@ "failedToSaveIntegration": { "message": "Fehler beim Speichern der Integration. Bitte versuche es später erneut." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Sie müssen Eigentümer der Organisation sein, um diese Aktion auszuführen." + }, "failedToDeleteIntegration": { - "message": "Failed to delete integration. Please try again later." + "message": "Löschen der Integration fehlgeschlagen. Bitte versuchen Sie es später erneut." }, "deviceIdMissing": { "message": "Geräte-ID fehlt" @@ -9783,7 +9839,7 @@ } }, "updateIntegrationButtonDesc": { - "message": "Update $INTEGRATION$", + "message": "$INTEGRATION$ aktualisieren", "placeholders": { "integration": { "content": "$1", @@ -9855,7 +9911,7 @@ "message": "Bearer-Token" }, "repositoryNameHint": { - "message": "Name of the repository to ingest into" + "message": "Name des Codespeichers, in den importiert werden soll" }, "index": { "message": "Index" @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Geringe KDF-Iterationen. Erhöhe deine Iterationen, um die Sicherheit deines Kontos zu steigern." - }, - "changeKDFSettings": { - "message": "KDF-Einstellungen ändern" - }, "secureYourInfrastructure": { "message": "Sichere deine Infrastruktur" }, @@ -10995,10 +11045,10 @@ "message": "Gefährdetes Passwort ändern" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." + "message": "Diese Zugangsdaten sind gefährdet und enthalten keine Website. Fügen Sie eine Website hinzu und ändern Sie das Passwort für mehr Sicherheit." }, "missingWebsite": { - "message": "Missing website" + "message": "Fehlende Webseite" }, "removeUnlockWithPinPolicyTitle": { "message": "Entsperren mit PIN entfernen" @@ -11016,22 +11066,27 @@ "message": "Diese Ereignisse sind nur Beispiele und spiegeln keine realen Ereignisse in deiner Bitwarden-Organisation wider." }, "viewEvents": { - "message": "View Events" + "message": "Ereignisse anzeigen" }, "cannotCreateCollection": { "message": "Kostenlose Organisationen können bis zu 2 Sammlungen haben. Upgrade auf ein kostenpflichtiges Abo, um mehr Sammlungen hinzuzufügen." }, "searchArchive": { - "message": "Search archive" + "message": "Archiv durchsuchen" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Keine Einträge im Archiv" }, "archivedItemsDescription": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Autofill-Vorschlägen ausgeschlossen." }, "businessUnit": { "message": "Geschäftsbereich" @@ -11188,10 +11243,10 @@ "description": "Error message shown when trying to add credit to a trialing organization without a billing address." }, "aboutThisSetting": { - "message": "About this setting" + "message": "Über diese Einstellung" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu bestimmen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um Ihr Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn Sie diesen Dienst nutzen." }, "billingAddress": { "message": "Rechnungsadresse" @@ -11311,7 +11366,7 @@ "message": "Maßnahme erforderlich: In den Zahlungsdetails fehlt eine Steueridentifikationsnummer. Wenn keine Steuernummer hinzugefügt wird, können deine Rechnungen zusätzliche Steuern enthalten." }, "moreBreadcrumbs": { - "message": "More breadcrumbs", + "message": "Weitere Navigationspfade", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." }, "addTaxId": { @@ -11355,46 +11410,46 @@ } }, "confirmKeyConnectorDomain": { - "message": "Confirm Key Connector domain" + "message": "Key Connector-Domain bestätigen" }, "requiredToVerifyBankAccountWithStripe": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "Zahlungen mit einem Bankkonto sind nur für Kunden in den Vereinigten Staaten verfügbar. Sie müssen Ihr Bankkonto verifizieren. Wir werden innerhalb der nächsten 1–2 Werktage eine kleine Einzahlung vornehmen. Wenn das Bankkonto nicht verifiziert wird, führt dies zu einer verpassten Zahlung und die Abonnement wird ausgesetzt." }, "verifyBankAccountWithStripe": { - "message": "We have made a micro-deposit to your bank account. This may take 1-2 business days. When you see the deposit in your account, you can verify your bank account. Failure to verify your bank account will result in a missed payment and your subscription will be suspended." + "message": "Wir haben eine kleine Einzahlung auf Ihr Bankkonto vorgenommen. Dies kann 1–2 Werktage dauern. Sobald Sie die Einzahlung auf Ihrem Konto sehen, können Sie Ihr Bankkonto verifizieren. Wenn das Bankkonto nicht verifiziert wird, führt dies zu einer verpassten Zahlung und Ihr Abonnement wird ausgesetzt." }, "verifyNow": { - "message": "Verify now." + "message": "Jetzt verifizieren." }, "additionalStorageGB": { - "message": "Additional storage GB" + "message": "Zusätzlicher Speicher GB" }, "additionalServiceAccountsV2": { - "message": "Additional machine accounts" + "message": "Zusätzliche Maschinenkonten" }, "secretsManagerSeats": { - "message": "Secrets Manager seats" + "message": "Secrets-Manager-Plätze" }, "additionalStorage": { - "message": "Additional Storage" + "message": "Zusätzlicher Speicher" }, "expandPurchaseDetails": { - "message": "Expand purchase details" + "message": "Kaufdetails erweitern" }, "collapsePurchaseDetails": { - "message": "Collapse purchase details" + "message": "Kaufdetails einklappen" }, "familiesMembership": { - "message": "Families membership" + "message": "Families-Mitgliedschaft" }, "planDescPremium": { - "message": "Complete online security" + "message": "Umfassende Online-Sicherheit" }, "planDescFamiliesV2": { - "message": "Premium security for your family" + "message": "Premium-Sicherheit für Ihre Familie" }, "planDescFreeV2": { - "message": "Share with $COUNT$ other user", + "message": "Mit $COUNT$ anderem Benutzer teilen", "placeholders": { "count": { "content": "$1", @@ -11403,37 +11458,37 @@ } }, "planDescEnterpriseV2": { - "message": "Advanced capabilities for any organization" + "message": "Erweiterte Funktionen für jede Organisation" }, "planNameCustom": { - "message": "Custom plan" + "message": "Individueller Plan" }, "planDescCustom": { - "message": "Bitwarden scales with businesses of all sizes to secure passwords and sensitive information. If you're part of a large enterprise, contact sales to request a quote." + "message": "Bitwarden wächst mit Unternehmen jeder Größe mit, um Passwörter und vertrauliche Informationen zu sichern. Wenn Sie Teil eines großen Unternehmens sind, kontaktieren Sie den Vertrieb, um ein Angebot anzufordern." }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Integrierter Authenticator" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Datenpannen-Überwachung" }, "andMoreFeatures": { - "message": "And more!" + "message": "Und mehr!" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Sicherer Dateispeicher" }, "familiesUnlimitedSharing": { - "message": "Unlimited sharing - choose who sees what" + "message": "Unbegrenztes Teilen – wählen Sie, wer was sieht" }, "familiesUnlimitedCollections": { - "message": "Unlimited family collections" + "message": "Unbegrenzte Familiensammlungen" }, "familiesSharedStorage": { - "message": "Shared storage for important family info" + "message": "Gemeinsamer Speicher für wichtige Familieninformationen" }, "limitedUsersV2": { - "message": "Up to $COUNT$ members", + "message": "Bis zu $COUNT$ Mitglieder", "placeholders": { "count": { "content": "$1", @@ -11442,7 +11497,7 @@ } }, "limitedCollectionsV2": { - "message": "Up to $COUNT$ collections", + "message": "Bis zu $COUNT$ Sammlungen", "placeholders": { "count": { "content": "$1", @@ -11451,13 +11506,13 @@ } }, "alwaysFree": { - "message": "Always free" + "message": "Immer kostenlos" }, "twoSecretsIncluded": { - "message": "2 secrets" + "message": "2 Geheimnisse" }, "projectsIncludedV2": { - "message": "$COUNT$ project(s)", + "message": "$COUNT$ Projekt(e)", "placeholders": { "count": { "content": "$1", @@ -11466,13 +11521,13 @@ } }, "secureItemSharing": { - "message": "Secure item sharing" + "message": "Sicheres Eintrags-Teilen" }, "scimSupport": { - "message": "SCIM support" + "message": "SCIM-Unterstützung" }, "includedMachineAccountsV2": { - "message": "$COUNT$ machine accounts", + "message": "$COUNT$ Maschinenkonten", "placeholders": { "count": { "content": "$1", @@ -11481,21 +11536,21 @@ } }, "enterpriseSecurityPolicies": { - "message": "Enterprise security policies" + "message": "Enterprise-Sicherheitsrichtlinien" }, "selfHostOption": { - "message": "Self-host option" + "message": "Self-Host-Option" }, "complimentaryFamiliesPlan": { - "message": "Complimentary families plan for all users" + "message": "Kostenloser Families-Plan für alle Benutzer" }, "strengthenCybersecurity": { - "message": "Strengthen cybersecurity" + "message": "Cybersicherheit stärken" }, "boostProductivity": { - "message": "Boost productivity" + "message": "Produktivität steigern" }, "seamlessIntegration": { - "message": "Seamless integration" + "message": "Nahtlose Integration" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 0c876ddd282..c0f2a338557 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Κρίσιμες εφαρμογές ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Ειδοποιημένα μέλη ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Δεν υπάρχουν στοιχεία στη λίστα." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Δεν έχετε άδεια να δείτε όλα τα στοιχεία σε αυτήν τη συλλογή." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Ο οργανισμός είναι απενεργοποιημένος." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "Η άδεια χρήσης έληξε." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Αναγνωριστικό SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Δώστε αυτό το αναγνωριστικό στα μέλη σας για να συνδεθείτε με SSO. Για να παρακάμψετε αυτό το βήμα, ρυθμίστε την ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Αποσύνδεση SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Ομάδα/Χρήστης" }, - "lowKdfIterations": { - "message": "Λίγες επαναλήψεις KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Ενημερώστε τις ρυθμίσεις κρυπτογράφησής σας για να ανταποκριθείτε στις νέες συστάσεις ασφαλείας και να βελτιώσετε την προστασία του λογαριασμού." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Λίγες επαναλήψεις KDF. Αυξήστε τις επαναλήψεις σας για να βελτιώσετε την ασφάλεια του λογαριασμού σας." - }, - "changeKDFSettings": { - "message": "Αλλαγή ρυθμίσεων KDF" - }, "secureYourInfrastructure": { "message": "Ασφαλίστε την υποδομή σας" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index edf4b59c5bd..f187d6484d3 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in the bin" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favourited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favourites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisation suspended" }, + "organizationIsSuspended": { + "message": "Organisation is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organisations cannot be accessed. Contact your organisation owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organisations cannot be accessed. Please contact your organisation owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organisations. Please contact your organisation owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organisations cannot be accessed. Contact your organisation owner for assistance." - }, "licenseIsExpired": { "message": "Licence is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organisation owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index ed092371092..b69c7f4488c 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in the bin" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favourited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favourites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisation is disabled." }, + "organizationIsSuspended": { + "message": "Organisation is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organisations cannot be accessed. Contact your organisation owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organisations cannot be accessed. Please contact your organisation owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organisations. Please contact your organisation owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organisations cannot be accessed. Contact your organisation owner for assistance." - }, "licenseIsExpired": { "message": "Licence is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organisation owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 39ee969de3b..8d51d1f4ded 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Krei novan salutan eron" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Sciigitaj membroj ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Estas neniu ero por listi" }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "La organizo suspendiĝis" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "Licenco eksvalidiĝis." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Malkonekti SSO-n" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupo/Uzanto" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Ŝanĝi agordojn pri KDF" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 7d76870ddc6..0cedc53a0ab 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Crear nuevo elemento de inicio de sesión" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Aplicaciones críticas ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Miembros notificados ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "No hay elementos que listar." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "No tiene los permisos para ver todos los elementos de esta colección." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "La organización está desactivada." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "No se puede acceder a las organizaciones suspendidas. Póngase en contacto con el propietario de su organización para obtener ayuda." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "No se pueden crear cuentas de servicio en organizaciones suspendidas. Póngase en contacto con el propietario de su organización para obtener ayuda." }, - "disabledOrganizationFilterError": { - "message": "No se puede acceder a los elementos que pertenecen a organizaciones que estén deshabilitadas. Por favor, póngase en contacto con el propietario de la organización para obtener ayuda." - }, "licenseIsExpired": { "message": "Licencia expirada." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Identificador SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Proporcione este ID a sus miembros para iniciar sesión con SSO. Para evitar este paso, configure ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Desenlazar SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupo/Usuario" }, - "lowKdfIterations": { - "message": "Iteraciones KDF bajas" - }, - "updateLowKdfIterationsDesc": { - "message": "Actualice sus ajustes de cifrado para cumplir con las nuevas recomendaciones de seguridad y mejorar la protección de la cuenta." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 83ff0028e0d..dfc120b75fd 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Loo uus kirje" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Teavitatud liikmed ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Puuduvad kirjed, mida kuvada." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Sul ei ole õigust vaadata kõiki asju selles kogus." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisatsioon on määramata ajaks peatatud" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Peatatud organisatsioonidele ei ole võimalik ligi pääseda. Palun kontakteeruge oma organisatsiooni haldajaga." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Peatatud organisatsioonides ei ole võimalik luua uusi kontosid. Palun kontakteeruge oma organisatsiooni haldajaga." }, - "disabledOrganizationFilterError": { - "message": "Organisatsiooni alla kuuluvatele kirjetele ei ole ligipääsu. Kontakteeru oma organisatsiooni omanikuga." - }, "licenseIsExpired": { "message": "Litsents on aegunud." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Ühenda SSO lahti" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 0dc74e0bb94..cc955354537 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Ez dago erakusteko elementurik." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Erakundea desgaituta dago." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Ezin da sartu desgaitutako erakundeetako elementuetara. Laguntza lortzeko, jarri harremanetan zure erakundearekin." - }, "licenseIsExpired": { "message": "Lizentzia iraungi da." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifikatzailea" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO deskonektatu" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 0211b3a7d12..973bfb3b413 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "ایجاد مورد ورود جدید" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "برنامه‌های حیاتی ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "اعضا مطلع شدند ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "هیچ موردی برای نمایش وجود ندارد." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "شما اجازه مشاهده همه موارد در این مجموعه را ندارید." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "سازمان از کار افتاده است" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "سازمان‌های تعلیق شده قابل دسترسی نیستند. لطفاً برای کمک با مالک سازمان خود تماس بگیرید." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "حساب‌های خدماتی را نمی‌توان در سازمان‌های معلق ایجاد کرد. لطفاً برای کمک با مالک سازمان خود تماس بگیرید." }, - "disabledOrganizationFilterError": { - "message": "موارد موجود در سازمان‌های غیرفعال، قابل دسترسی نیستند. برای دریافت کمک با مالک سازمان خود تماس بگیرید." - }, "licenseIsExpired": { "message": "مجوز منقضی شده است." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "شناسه SSO" }, - "ssoIdentifierHintPartOne": { - "message": "این شناسه را در اختیار اعضای خود قرار دهید تا با SSO وارد شوند. برای دور زدن این مرحله، راه‌اندازی کنید ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "لغو پیوند SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "گروه/کاربر" }, - "lowKdfIterations": { - "message": "تکرار KDF کم" - }, - "updateLowKdfIterationsDesc": { - "message": "تنظیمات رمزگذاری خود را برای رعایت توصیه‌های امنیتی جدید و بهبود حفاظت از حساب به‌روزرسانی کنید." - }, "kdfSettingsChangeLogoutWarning": { "message": "ادامه دادن باعث خروج شما از تمام نشست‌های فعال خواهد شد. برای ادامه باید دوباره وارد شوید و در صورت فعال بودن، ورود دو مرحله‌ای را کامل کنید. توصیه می‌کنیم قبل از تغییر تنظیمات رمزنگاری، از گاوصندوق خود خروجی بگیرید تا از دست رفتن داده‌ها جلوگیری شود." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "تعداد تکرارهای KDF پایین است. برای افزایش امنیت حساب کاربری خود، تعداد تکرارها را افزایش دهید." - }, - "changeKDFSettings": { - "message": "تغییر تنظیمات KDF" - }, "secureYourInfrastructure": { "message": "زیرساخت خود را ایمن کنید" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 98f37b43cf4..f717ca1e5a5 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Luo uusi kirjautumiskohde" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kriittiset sovellukset ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Ilmoitetut jäsenet ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Näytettäviä kohteita ei ole." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Sinulla ei ole kokoelman kaikkien kohteiden tarkastelun sallivia käyttöoikeuksia." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisaatio on jäädytetty" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Jäädytettyjen organisaatioiden kohteet eivät ole käytettävissä. Ole yhteydessä organisaatiosi omistajaan saadaksesi apua." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Palvelutilien luonti ei ole mahdollista jäädytetyissä organisaatioissa. Ole yhteydessä organisaatiosi omistajaan saadaksesi apua." }, - "disabledOrganizationFilterError": { - "message": "Jäädytettyjen organisaatioiden kohteet eivät ole käytettävissä. Ole yhteydessä organisaation omistajaan saadaksesi apua." - }, "licenseIsExpired": { "message": "Lisenssi on erääntynyt." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Kertakirjautumistunniste" }, - "ssoIdentifierHintPartOne": { - "message": "Toimita tämä tunniste jäsenillesi kertakirjautumista varten. Ohita vaihe määritämällä ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Poista kertakirjautumisliitos" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Ryhmä/käyttäjä" }, - "lowKdfIterations": { - "message": "Alhainen KDF-toistomäärä" - }, - "updateLowKdfIterationsDesc": { - "message": "Päivitä salausasetuksesi uusien suojaussuositusten mukaisiksi ja vahvista tilisi suojausta." - }, "kdfSettingsChangeLogoutWarning": { "message": "Jos jatkat, kirjataan kaikki aktiiviset istunnot ulos, jonka jälkeen sinun on kirjauduttava sisään uudelleen ja suoritettava mahdollisesti määritetty kaksivaiheinen tunnistautuminen. Tietojesi säilyvyyden varmistamiseksi suosittelemme, että viet holvisi sisällön ennen salausasetustesi muuttamista." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Alhainen KDF-toistojen määrä. Paranna tilisi suojausta korottamalla määrää." - }, - "changeKDFSettings": { - "message": "Muuta KDF-asetuksia" - }, "secureYourInfrastructure": { "message": "Suojaa infrastruktuurisi" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 8bb6d1fa53c..3e11a4eb943 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Walang maililistang item." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Wala kang pahintulot na makita lahat ng mga item sa koleksyong ito." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisasyon ay suspindido." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Mga item sa mga naka-suspindong Organisasyon ay hindi ma-access. Mangyaring makipag-ugnayan sa may-ari ng iyong Organisasyon para sa tulong." - }, "licenseIsExpired": { "message": "Ang lisensya ay expired na." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Tagatukoy ng SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "I-unlink ang SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupo/User" }, - "lowKdfIterations": { - "message": "Mababang KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index e0c6cf2287d..d1d00d7da63 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Créer un nouvel élément de connexion" }, - "criticalApplicationsActivityDescription": { - "message": "Une fois que vous avez marqué des applications comme critiques, elles s'afficheront ici." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Une fois que vous avez marqué des applications comme critiques, elles s'afficheront ici" + }, + "viewAtRiskMembers": { + "message": "Afficher les membres à risque" + }, + "viewAtRiskApplications": { + "message": "Afficher les applications à risque" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ sur $TOTAL$ applications critiques sont à risque en raison de mots de passe à risque", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Applications critiques ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications à risque", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membres notifiés ($COUNT$)", "placeholders": { @@ -137,7 +165,7 @@ "membersAtRiskActivityDescription": { "message": "Membres pouvant modifier les éléments à risque pour les applications critiques" }, - "membersAtRisk": { + "membersAtRiskCount": { "message": "$COUNT$ membres à risque", "placeholders": { "count": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Aucun élément à afficher." }, + "noItemsInTrash": { + "message": "Aucun élément dans la corbeille" + }, + "noItemsInTrashDesc": { + "message": "Les éléments que vous supprimez apparaîtront ici et seront définitivement supprimés après 30 jours" + }, + "noItemsInVault": { + "message": "Aucun élément dans le coffre" + }, + "emptyVaultDescription": { + "message": "Le coffre protège bien plus que vos mots de passe. Stockez vos identifiants, IDs, cartes et notes en toute sécurité ici." + }, + "emptyFavorites": { + "message": "Vous n'avez mis aucun élément en favori" + }, + "emptyFavoritesDesc": { + "message": "Ajouter les éléments fréquemment utilisés aux favoris pour un accès rapide." + }, + "noSearchResults": { + "message": "Aucun résultat de recherche retourné" + }, + "clearFiltersOrTryAnother": { + "message": "Effacer les filtres ou essayer un autre terme de recherche" + }, "noPermissionToViewAllCollectionItems": { "message": "Vous n'avez pas l'autorisation d'afficher tous les éléments de cette collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "L'organisation est désactivée." }, + "organizationIsSuspended": { + "message": "L'organisation est suspendue" + }, + "organizationIsSuspendedDesc": { + "message": "Les éléments des organisations suspendues ne sont pas accessibles. Contactez le propriétaire de votre organisation pour obtenir de l'aide." + }, "secretsAccessSuspended": { "message": "Impossible d'accéder aux organisations suspendues. Veuillez contacter le propriétaire de votre organisation pour obtenir de l'aide." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Les comptes de service ne peuvent pas être créés dans les organisations suspendues. Veuillez contacter le propriétaire de votre organisation pour obtenir de l'aide." }, - "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." - }, "licenseIsExpired": { "message": "La licence a expiré." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Identifiant SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Fournir cet ID à vos membres pour se connecter avec SSO. Pour contourner cette étape, configurer ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Délier SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Groupe/Utilisateur" }, - "lowKdfIterations": { - "message": "Itérations KDF basses" - }, - "updateLowKdfIterationsDesc": { - "message": "Mettez à jour vos paramètres de chiffrement pour répondre à de nouvelles recommandations de sécurité et améliorer la protection de votre compte." - }, "kdfSettingsChangeLogoutWarning": { "message": "Effectuer cette action vous déconnectera de toutes les sessions actives. Vous devrez vous connecter à nouveau et compléter votre authentification à 2 facteurs, s'il y a lieu. Nous vous recommandons d'exporter votre coffre avant de modifier vos paramètres de chiffrement pour prévenir la perte de vos données." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Impossible d'enregistrer l'intégration. Veuillez réessayer plus tard." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Vous devez être le propriétaire de l'organisation pour effectuer cette action." + }, "failedToDeleteIntegration": { "message": "La suppression de l'intégration échouée. Veuillez réessayer plus tard." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Nombres d'itérations KDF bas. Augmentez vos itérations pour améliorer la sécurité de votre compte." - }, - "changeKDFSettings": { - "message": "Modifier les paramètres KDF" - }, "secureYourInfrastructure": { "message": "Sécurisez votre infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Rechercher dans l'archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Aucun élément dans l'archive" diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 8c163ef4f6a..78589d8a3b6 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 72a63ab210a..99a172a71d1 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "צור פריט כניסה חדש" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "יישומים קריטיים ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "חברים שהודיעו להם ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "אין פריטים להצגה ברשימה." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "אין לך הרשאה להציג את כל הפריטים באוסף זה." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "הארגון הושעה" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "לא ניתן לגשת אל ארגונים מושעים. נא לפנות לבעל הארגון שלך עבור סיוע." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "לא ניתן ליצור חשבונות שירות בארגונים מושעים. נא לפנות אל בעל הארגון שלך עבור סיוע." }, - "disabledOrganizationFilterError": { - "message": "לא ניתן לגשת לפריטים בארגון מושעה. פנה אל בעל הארגון שלך עבור סיוע." - }, "licenseIsExpired": { "message": "תוקף הרשיון הסתיים." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "מזהה SSO" }, - "ssoIdentifierHintPartOne": { - "message": "ספק את המזהה הזה לחברים שלך כדי שיכנסו עם SSO. כדי לעקוף את השלב הזה, הגדר ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "נתק SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "קבוצה/משתמש" }, - "lowKdfIterations": { - "message": "חזרות KDF נמוכות" - }, - "updateLowKdfIterationsDesc": { - "message": "שדרג את הגדרות ההצפנה שלך כדי לעמוד בהמלצות אבטחה חדשות ולשפר את הגנת החשבון." - }, "kdfSettingsChangeLogoutWarning": { "message": "המשך התהליך יוציא אותך מכל ההפעלות הפעילות שלך. תידרש להיכנס חזרה כדי להמשיך כניסה דו-שלבית, אם ישנה. אנו ממליצים על ייצוא הכספת שלך לפני שינוי הגדרות ההצפנה שלך כדי למנוע איבוד נתונים." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "שמירת האינטגרציה נכשלה. נא לנסות שוב מאוחר יותר." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "חזרות KDF נמוכות. הגדל את מספר החזרות שלך כדי לשפר את האבטחה של חשבונך." - }, - "changeKDFSettings": { - "message": "שנה הגדרות KDF" - }, "secureYourInfrastructure": { "message": "אבטח את התשתית שלך" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 9cdcabf7423..96fdeacc6ff 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 66eca3f69a9..6ca05ee3f62 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Stvori novu stavku prijave" }, - "criticalApplicationsActivityDescription": { - "message": "Aplikacije označene kao kritične će biti prikazane ovdje." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "Rizični korisnici" + }, + "viewAtRiskApplications": { + "message": "Rizične aplikacije" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ od $TOTAL$ kritilčnih aplikacija su ugrožene zbog rizičnih lozinki", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritične aplikacije ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ aplikacija/e ugroženo", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Obaviješteni članovi ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Članovi koji mogu uređivati stavke za aplikacije označene kao kritične" }, - "membersAtRisk": { - "message": "Rizičnih članova: $COUNT$", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Nema stavki za prikaz." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Nemaš prava vidjeti sve stavke u ovoj zbirci." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organizacija suspendirana" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Stavkama u suspendiranoj Organizaciji se ne može pristupiti. Kontaktiraj vlasnika Organizacije za pomoć." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Nije moguće stvoriti Servisne račune u suspendiranim organizacijama. Kontaktiraj vlasnika tvoje organizacije za pomoć." }, - "disabledOrganizationFilterError": { - "message": "Stavkama u suspendiranoj Organizaciji se ne može pristupiti. Kontaktiraj vlasnika Organizacije za pomoć." - }, "licenseIsExpired": { "message": "Licenca je istekla." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifikator" }, - "ssoIdentifierHintPartOne": { - "message": "Dajte ovaj ID svojim članovima za prijavu putem SSO-a. Za zaobilazak ovog koraka, postavi ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Odspoji SSO" @@ -5558,10 +5617,10 @@ "message": "Pravila tvrtke onemogućuju spremanje stavki u osobni trezor. Promijeni vlasništvo stavke na tvrtku i odaberi dostupnu Zbirku." }, "desktopAutotypePolicy": { - "message": "Desktop Autotype Default Setting" + "message": "Zadana postavka automatskog tipkanja na radnoj površini" }, "desktopAutotypePolicyDesc": { - "message": "Turn Desktop Autotype ON by default for members. Members can turn Autotype off manually in the Desktop client.", + "message": "Uključi automatsko tipkanje na radnoj površini prema zadanim postavkama za članove. Članovi mogu ručno isključiti automatsko tipkanje u klijentu za radnu površinu.", "description": "This policy will enable Desktop Autotype by default for members on Unlock." }, "disableSend": { @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupa/korisnik" }, - "lowKdfIterations": { - "message": "Niske KDF iteracije" - }, - "updateLowKdfIterationsDesc": { - "message": "Ažuriraj svoje postavke enkripcije kako bi zadovoljili nove sigurnosne preporuke i poboljšali zaštitu računa." - }, "kdfSettingsChangeLogoutWarning": { "message": "Nastavkom ćeš se odjaviti iz svih aktivnih sesija. Morat ćeš se ponovno prijaviti i izvršiti dvostruku autentifikaciju, ako je aktivna. Preporučujemo izvoz tvog trezora prije promjene postavki enkripcije kako bi spriječili gubitak podataka." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Spremanje integracije nije uspjelo. Pokušaj ponovno kasnije." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Za ovo, moraš biti vlasnik organizacije." + }, "failedToDeleteIntegration": { "message": "Brisanje integracije nije uspjelo. Pokušaj ponovno kasnije." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Niske KDF iteracije. Povećaj svoje iteracije za poboljšanje sigurnost računa." - }, - "changeKDFSettings": { - "message": "Promijeni KDF postavke" - }, "secureYourInfrastructure": { "message": "Osiguraj svoju infrastrukturu" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Pretraži arhivu" }, - "archive": { - "message": "Arhiva" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Nema stavki u arhivi" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 910447f236c..ba533d302f5 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Új bejelentkezési elem létrehozása" }, - "criticalApplicationsActivityDescription": { - "message": "Miután megjelöltük a kritikus alkalmazásokat, azok itt jelennek meg." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "A kritikus alkalmazások megjelölésével azok itt jelennek meg." + }, + "viewAtRiskMembers": { + "message": "Kockázatos tagok megtekintése" + }, + "viewAtRiskApplications": { + "message": "Kockázatos alkalmazások megtekintése" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ / $TOTAL$ kritikus alkalmazás veszélyben van a kockázatos jelszavak miatt.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritikus alkalmazások ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ kockázatos alkalmazás", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Értesített tagok ($COUNT$)", "placeholders": { @@ -137,7 +165,7 @@ "membersAtRiskActivityDescription": { "message": "Tagok szerkesztési hozzáféréssel a kritikus alkalmazások veszélyeztetett elemeihez" }, - "membersAtRisk": { + "membersAtRiskCount": { "message": "$COUNT$ kockázatos tag", "placeholders": { "count": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Nincsenek megjeleníthető elemek." }, + "noItemsInTrash": { + "message": "Nincs elem a lomtárban." + }, + "noItemsInTrashDesc": { + "message": "A törölt elemek itt jelennek meg és 30 nap elteltével véglegesen törlődnek." + }, + "noItemsInVault": { + "message": "Nincsenek elemek a széfben." + }, + "emptyVaultDescription": { + "message": "A széf nemcsak a jelszavakat védi. Itt biztonságosan tárolhatjuk a bejelentkezési adatokat, egyéb azonosítókat, kártyákat és jegyzeteket." + }, + "emptyFavorites": { + "message": "Nem lett kedvencnek minősítve egyetlen elem sem." + }, + "emptyFavoritesDesc": { + "message": "Gyakran használt elemek hozzáadása a kedvencekhez a gyors hozzáférés érdekében." + }, + "noSearchResults": { + "message": "Nincsenek visszakapott keresési eredmények." + }, + "clearFiltersOrTryAnother": { + "message": "Töröljük a szűrőket vagy próbálkozzunk másik keresési kifejezéssel." + }, "noPermissionToViewAllCollectionItems": { "message": "Nincs jogosultság a gyűjtemény összes elemének megtekintésére." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "A szervezet letiltásra került." }, + "organizationIsSuspended": { + "message": "A szervezet felfüggesztésre került." + }, + "organizationIsSuspendedDesc": { + "message": "A letiltott szervezetek elemei nem érhetők el. Vegyük fel a kapcsolatot a szervezet tulajdonosával segítségért." + }, "secretsAccessSuspended": { "message": "A felfüggesztett szervezetekhez nem lehet hozzáférni. Segítségért forduljunk a szervezet tulajdonosához." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "A felfüggesztett szervezetekben nem hozhatók létre szolgáltatásfiókok. Segítségért forduljunk a szervezet tulajdonosához." }, - "disabledOrganizationFilterError": { - "message": "A letiltott szervezetek elemei nem érhetők el. Vegyük fel a kapcsolatot a szervezet tulajdonosával segítségért." - }, "licenseIsExpired": { "message": "A licensz lejárt." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Egyszeri azonosító" }, - "ssoIdentifierHintPartOne": { - "message": "Adjuk meg ezt az azonosítót a tagoknak a bejelentkezéshez SSO-val. A lépés megkerüléséhez üzemeljük be ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Adjuk meg ezt az azonosítót a tagokinak az SSO-val bejelentkezéshez. A tagok kihagyhatják ennek az azonosítónak a megadását az SSO során, ha egy igényelt tartomány be van állítva.", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "További információ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO szétkapcsolása" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Csoport/Felhasználó" }, - "lowKdfIterations": { - "message": "Alacsony KDF iterációk" - }, - "updateLowKdfIterationsDesc": { - "message": "Frissítsük a titkosítási beállításokat, hogy megfeleljünk az új biztonsági ajánlásoknak és javítsuk a fiókvédelmet." - }, "kdfSettingsChangeLogoutWarning": { "message": "A folytatás kijelentkeztet az összes aktív munkamenetből. Újra be kell jelentkezni és kétlépcsős bejelentkezést kell végrehajtani, ha van ilyen. Célszerű a titkosítási beállítások módosítása előtt a széf exportálása az adatvesztés elkerülése érdekében." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Nem sikerült menteni az integrációt. Próbáljuk újra később." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Ennek a műveletnek a végrehajtásához a szervezet tulajdonosának kell lenni." + }, "failedToDeleteIntegration": { "message": "Nem sikerült törölni az integrációt. Próbáljuk újra később." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Alacsony a KDF iterációk száma. Növeljük az iterációk számát a fiók biztonságának javítása érdekében." - }, - "changeKDFSettings": { - "message": "KDF beállítások megváltoztatása" - }, "secureYourInfrastructure": { "message": "Infrastruktúra biztosítása" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Keresés archívum" }, - "archive": { - "message": "Archívum" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Nincs elem az archívumban." diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 39f4232a707..2de26459ff0 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Buat entri masuk baru" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Semua aplikasi ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Anggota yang diberitahukan ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Tidak ada item yang dapat dicantumkan." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisasi dinonaktifkan." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Item di Organisasi yang dinonaktifkan tidak bisa diakses. Hubungi pemilik Organisasi Anda untuk mendapatkan bantuan." - }, "licenseIsExpired": { "message": "Lisensi sudah kadaluarsa." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Batalkan tautan SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Kelompok/Pengguna" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 3875bcb42f2..daeab7df8a1 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Crea nuovo elemento di login" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Applicazioni critiche ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membri notificati ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Non ci sono elementi da mostrare." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Non hai i permessi necessari per visualizzare tutti gli elementi in questa raccolta." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organizzazione disabilitata" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Non è possibile accedere alle organizzazioni disabilitate. Contatta il proprietario della tua organizzazione per assistenza." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Non è possibile creare account di servizio nelle organizzazioni disabilitate. Contatta il proprietario della tua organizzazione per assistenza." }, - "disabledOrganizationFilterError": { - "message": "Non è possibile accedere agli elementi nelle organizzazioni disabilitate. Contatta il proprietario della tua organizzazione per assistenza." - }, "licenseIsExpired": { "message": "La licenza è scaduta." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Identificativo SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Fornisci questo ID ai tuoi membri per accedere con SSO. Per saltare questo passaggio, configura la ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Scollega SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Gruppo/Utente" }, - "lowKdfIterations": { - "message": "Iterazioni KDF basse" - }, - "updateLowKdfIterationsDesc": { - "message": "Aggiorna le tue impostazioni di crittografia per soddisfare le nuove raccomandazioni sulla sicurezza e migliorare la protezione del tuo account." - }, "kdfSettingsChangeLogoutWarning": { "message": "Procedere ti farà uscire da tutte le sessioni attive. Dovrai accedere di nuovo e completare la verifica in due passaggi, se impostata. Ti consigliamo di esportare la tua cassaforte prima di cambiare le impostazioni di crittografia per prevenire perdite di dati." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Impossibile salvare l'integrazione. Riprova più tardi." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Iterazioni KDF basse. Aumenta le tue iterazioni per migliorare la sicurezza del tuo account." - }, - "changeKDFSettings": { - "message": "Cambia impostazioni KDF" - }, "secureYourInfrastructure": { "message": "Proteggi la tua infrastruttura" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 3f2a2ada371..bf65ef30a28 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "新しいログインアイテムを作成" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "特に重要なアプリ ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "通知済みメンバー ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "表示するアイテムがありません" }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "このコレクション内のアイテムをすべて表示する権限がありません。" }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "組織は無効です。" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "一時停止された組織にはアクセスできません。組織の所有者にお問い合わせください。" }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "一時停止中の組織ではサービスアカウントを作成できません。組織の所有者に問い合わせてください。" }, - "disabledOrganizationFilterError": { - "message": "無効な組織のアイテムにアクセスすることはできません。組織の所有者に連絡してください。" - }, "licenseIsExpired": { "message": "ライセンスの有効期限が切れています。" }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO 識別子" }, - "ssoIdentifierHintPartOne": { - "message": "SSO でログインできるようこの ID をメンバーに提供してください。この手順をバイパスするには、以下の設定をしてください:", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO のリンクを解除" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "グループ/ユーザー" }, - "lowKdfIterations": { - "message": "低 KDF イテレーション" - }, - "updateLowKdfIterationsDesc": { - "message": "新しいセキュリティの推奨事項に対応し、アカウントの保護を向上させるために、暗号化設定を更新してください。" - }, "kdfSettingsChangeLogoutWarning": { "message": "続行すると、すべてのアクティブなセッションからログアウトします。再度ログインし、2段階認証を完了する必要があります。 暗号化設定を変更する前に、保管庫をエクスポートしてデータの損失を防ぐことをおすすめします。" }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "KDF 反復回数が少ないです。あなたのアカウントのセキュリティを向上させるために反復回数を増やしてください。" - }, - "changeKDFSettings": { - "message": "KDF 設定の変更" - }, "secureYourInfrastructure": { "message": "インフラストラクチャの保護" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 170b989ff5d..ad1e820c179 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "არაა საგნები სიაში ჩამოსათველალდ." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "თქვენ არ გაქვთ უფლება ნახოთ ყველა საგანი ამ კოლექციაში." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 526b7567d99..fa4c124416d 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 13f72bed6c0..08dfc457843 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "ಪಟ್ಟಿ ಮಾಡಲು ಯಾವುದೇ ಐಟಂಗಳಿಲ್ಲ." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "ಸಂಘಟನೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "ಪರವಾನಗಿ ಅವಧಿ ಮೀರಿದೆ." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "ಎಸ್‌ಎಸ್‌ಒ ಅನ್ಲಿಂಕ್ ಮಾಡಿ" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 33ccaee2b5c..53af3e5313f 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "항목이 없습니다." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "이 컬렉션의 모든 항목을 볼 권한이 없습니다." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "조직이 비활성화됨" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "라이선스가 만료되었습니다." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO 연결 해제" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index bdf7dbef0fb..7b6d22e3679 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Izveidot jaunu pieteikšanās vienumu" }, - "criticalApplicationsActivityDescription": { - "message": "Tiklīz lietotnes tiks atzīmētas kā būtiskas, tās tiks parādītas šeit." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Tikldīz lietotnes tiks atzīmētas kā būtiskas, tās tiks parādītas šeit" + }, + "viewAtRiskMembers": { + "message": "Apskatīt riskam pakļautos dalībniekus" + }, + "viewAtRiskApplications": { + "message": "Apskatīt riskam pakļautās lietotnes" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ no $TOTAL$ būtiskajām lietotnēm ir apdraudētas riskam pakļautu paroļu dēļ", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritiskās lietotnes ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ lietotnes ir pakļautas riskam", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Apziņotie dalībnieki ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Dalībnieki ar labošanas piekluvi riskam pakļautajiem vienumiem būtiskajām lietotnēm" }, - "membersAtRisk": { - "message": "$COUNT$ riskam pakļauti dalībnieki", + "membersAtRiskCount": { + "message": "$COUNT$ dalībnieki ir pakļauti riskam", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Nav vienumu, ko parādīt." }, + "noItemsInTrash": { + "message": "Atkritnē nav vienumu" + }, + "noItemsInTrashDesc": { + "message": "Izdzēstie vienumi parādīsies šeit, un tie tiks neatgriezeniski izdzēsti pēc 30 dienām" + }, + "noItemsInVault": { + "message": "Glabātavā nav vienumu" + }, + "emptyVaultDescription": { + "message": "Glabātava aizsargā vairāk kā tikai paroles. Drošā veidā glabā šeit pieteikšanās vienumus, identitātes, kartes un piezīmes!" + }, + "emptyFavorites": { + "message": "Izlasē nav neviena vienuma" + }, + "emptyFavoritesDesc": { + "message": "Šeit ir pievienojami bieži izmantoti vienumi ātrākai piekļuvei." + }, + "noSearchResults": { + "message": "Nekas netika atrasts" + }, + "clearFiltersOrTryAnother": { + "message": "Jānotīra atsijātāji vai jāmēģina cits meklēšanas vaicājums" + }, "noPermissionToViewAllCollectionItems": { "message": "Nav atļaujas apskatīt visus šī krājuma vienumus." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Apvienība ir atspējota." }, + "organizationIsSuspended": { + "message": "Apvienība ir apturēta" + }, + "organizationIsSuspendedDesc": { + "message": "Apturētu apvienību vienumiem nevar piekļūt. Jāsazinās ar apvienības īpašnieku, lai iegūtu palīdzību." + }, "secretsAccessSuspended": { "message": "Apturētām apvienībām nav iespējams piekļūt. Lūgums vērsties pie savas apvienības īpašnieka pēc palīdzības." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Pakalpojumu kontus nav iespējams izveidot apturētās apvienībās. Lūgums vērsties pie savas apvienības īpašnieka pēc palīdzības." }, - "disabledOrganizationFilterError": { - "message": "Atspējotu apvienību vienumiem nevar piekļūt. Jāsazinās ar apvienības īpašnieku, lai iegūtu palīdzību." - }, "licenseIsExpired": { "message": "Ir beidzies licences izmantošanas laiks." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Vienotās pieteikšanās identifikators" }, - "ssoIdentifierHintPartOne": { - "message": "Šis Id ir sniedzams dalībniekiem, lai pieteiktos ar vienoto pieteikšanos. Lai apietu šo soli, jāuzstāda ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Atsaistīt SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Kopa/Lietotājs" }, - "lowKdfIterations": { - "message": "Zems KDF atkārtojumu skaits" - }, - "updateLowKdfIterationsDesc": { - "message": "Jāatjaunina šifrēšanas iestatījumi, lai atbilstu jaunajiem drošības ieteikumiem un uzlabotu konta aizsardzību." - }, "kdfSettingsChangeLogoutWarning": { "message": "Turpinot notiks atteikšanās no visām esošajām sesijām. Būs atkārtoti jāpiesakās un jāpabeidz divpakāpju pieteikšanās, ja tāda ir. Mēs iesakām pirms šifrēšanas iestatījumu mainīšanas izgūt glabātavas saturu, lai novērstu datu zudumu." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Neizdevās saglabāt iekļaušanu. Lūgums vēlāk mēģināt vēlreiz." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Jābūt apvienības īpašniekam, lai veiktu šo darbību." + }, "failedToDeleteIntegration": { "message": "Neizdevās izdzēst iekļaušanu. Lūgums vēlāk mēģināt vēlreiz." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Zems KDF atkārtojumu skaits. Tas jāpalielina, lai uzlabotu sava konta drošību." - }, - "changeKDFSettings": { - "message": "Mainīt KDF iestatījumus" - }, "secureYourInfrastructure": { "message": "Nodrošini savu infrastruktūru" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Meklēt arhīvā" }, - "archive": { - "message": "Arhivēt" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Arhīvā nav vienumu" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 7f345a5b85c..2c1fde02227 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "പ്രദർശിപ്പിക്കാൻ ഇനങ്ങളൊന്നുമില്ല." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "സംഘടന അപ്രാപ്‌തമാക്കി." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "ലൈസൻസ് കാലഹരണപ്പെട്ടു." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index b25c6fb16ea..c1aef5bc376 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "नवीन लॉगिन आयटम तयार करा" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 526b7567d99..fa4c124416d 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 3cfcd87ec64..bf44b5f7397 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Det er ingen elementer å vise." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Du har ikke tillatelse til å se alle elementer i denne samlingen." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisasjonen er skrudd av." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Gjenstander i deaktiverte organisasjoner kan ikke åpnes. Ta kontakt med eieren av organisasjonen for hjelp." - }, "licenseIsExpired": { "message": "Lisensen har utløpt." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO-identifikator" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Koble fra SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Gruppe/Bruker" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 7dff2786a5d..29d4bd0da20 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 1ad5b20745a..f8db72ffe8c 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Nieuw login item aanmaken" }, - "criticalApplicationsActivityDescription": { - "message": "Als je toepassingen als belangrijk markeert, verschijnen ze hier." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Als je toepassingen als belangrijk markeert, verschijnen ze hier" + }, + "viewAtRiskMembers": { + "message": "Leden met risico bekijken" + }, + "viewAtRiskApplications": { + "message": "Applicaties met risico bekijken" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ van de $TOTAL$ kritische applicaties zijn in gevaar als gevolg van risicovolle wachtwoorden", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Belangrijke applicaties ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applicaties in gevaar", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Geînformeerde leden ($COUNT$)", "placeholders": { @@ -137,7 +165,7 @@ "membersAtRiskActivityDescription": { "message": "Leden met toegang voor bewerken van risico-items voor belangrijke applicaties" }, - "membersAtRisk": { + "membersAtRiskCount": { "message": "$COUNT$ leden lopen risico", "placeholders": { "count": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Er zijn geen items om weer te geven." }, + "noItemsInTrash": { + "message": "Geen items in prullenbak" + }, + "noItemsInTrashDesc": { + "message": "Items die je verwijdert verschijnen hier en worden na 30 dagen definitief verwijderd" + }, + "noItemsInVault": { + "message": "Geen items in de kluis" + }, + "emptyVaultDescription": { + "message": "De kluis beschermt meer dan alleen je wachtwoorden. Sla hier beveiligde inloggegevens, ID's, kaarten en notities op." + }, + "emptyFavorites": { + "message": "Je hebt geen favoriete items" + }, + "emptyFavoritesDesc": { + "message": "Voeg veelgebruikte items toe aan favorieten voor snelle toegang." + }, + "noSearchResults": { + "message": "Geen resultaten teruggekregen" + }, + "clearFiltersOrTryAnother": { + "message": "Wis filters of probeer een andere zoekterm" + }, "noPermissionToViewAllCollectionItems": { "message": "Je hebt geen rechten om alle items in deze collectie te zien." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisatie opgeschort" }, + "organizationIsSuspended": { + "message": "Organisatie is opgeschort" + }, + "organizationIsSuspendedDesc": { + "message": "Je kunt items in opgeschorte organisaties niet benaderen. Neem contact op met de eigenaar van je organisatie voor hulp." + }, "secretsAccessSuspended": { "message": "Opgeschorte organisaties zijn niet toegankelijk. Neem contact op met de eigenaar van je organisatie voor hulp." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Je kunt geen service accounts aanmaken in opgeschorte organisaties. Neem contact op met de eigenaar van je organisatie voor hulp." }, - "disabledOrganizationFilterError": { - "message": "Je kunt uitgeschakelde items in een organisatie niet benaderen. Neem contact op met de eigenaar van je organisatie voor hulp." - }, "licenseIsExpired": { "message": "Licentie verlopen." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO Identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Geef dit ID aan je leden voor inloggen met SSO. Om deze stap te omzeilen, configureer je ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Geef dit ID aan je leden om in te loggen met SSO. Leden kunnen het invoeren van deze identificatie overslaan tijdens SSO als een geclaimde domein is ingesteld ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Meer informatie", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO ontkoppelen" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Groep/Gebruiker" }, - "lowKdfIterations": { - "message": "Weinig KDF-iteraties" - }, - "updateLowKdfIterationsDesc": { - "message": "Werk je versleutelingsinstellingen bij om aan de nieuwe beveiligingsaanbevelingen te voldoen en de bescherming van je account te verbeteren." - }, "kdfSettingsChangeLogoutWarning": { "message": "Als je doorgaat, log je uit van alle actieve sessies. Je zult opnieuw moeten inloggen en, indien van toepassing, tweestapsverificatie moeten voltooien. We raden aan om je kluis te exporteren voordat je je versleutelingsinstellingen wijzigt om gegevensverlies te voorkomen." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Opslaan van integratie mislukt. Probeer het later opnieuw." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Je moet de eigenaar van de organisatie zijn om deze actie uit te voeren." + }, "failedToDeleteIntegration": { "message": "Verwijderen van integratie mislukt. Probeer het later opnieuw." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Laag aantal KDF-iteraties. Verhoog je iteraties om de veiligheid van je account te verbeteren." - }, - "changeKDFSettings": { - "message": "KDF-instellingen wijzigen" - }, "secureYourInfrastructure": { "message": "Beveilig je infrastructuur" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 54323d9b12a..ec6da6ee754 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Det er inga oppføringar å lista opp." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 526b7567d99..fa4c124416d 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 8254d28f142..20a8331b61e 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Utwórz nowy element logowania" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Aplikacje krytyczne ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Powiadomieni członkowie ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Brak elementów." }, + "noItemsInTrash": { + "message": "Brak elementów w koszu" + }, + "noItemsInTrashDesc": { + "message": "Usunięte elementy pojawią się tutaj i zostaną trwale usunięte po 30 dniach" + }, + "noItemsInVault": { + "message": "Brak elementów w sejfie" + }, + "emptyVaultDescription": { + "message": "Sejf chroni nie tylko hasła. Przechowuj bezpiecznie dane logowania, identyfikatory, karty i notatki." + }, + "emptyFavorites": { + "message": "Brak ulubionych elementów" + }, + "emptyFavoritesDesc": { + "message": "Dodaj do ulubionych często używane elementy dla szybkiego dostępu." + }, + "noSearchResults": { + "message": "Brak pasujących elementów" + }, + "clearFiltersOrTryAnother": { + "message": "Wyczyść filtry lub użyj innej frazy" + }, "noPermissionToViewAllCollectionItems": { "message": "Nie masz uprawnień do przeglądania wszystkich elementów w tej kolekcji." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organizacja została zawieszona" }, + "organizationIsSuspended": { + "message": "Organizacja została zawieszona" + }, + "organizationIsSuspendedDesc": { + "message": "Nie można uzyskać dostępu do elementów w zawieszonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." + }, "secretsAccessSuspended": { "message": "Nie można uzyskać dostępu do zawieszonych organizacji. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Konta serwisowe nie mogą być tworzone w zawieszonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." }, - "disabledOrganizationFilterError": { - "message": "Nie można uzyskać dostępu do elementów w zawieszonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." - }, "licenseIsExpired": { "message": "Licencja wygasła." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Identyfikator SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Podaj ten identyfikator swoim członkowm, aby zalogować się za pomocą SSO. Aby pominąć ten krok, ustaw ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Odłącz logowanie jednokrotne SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupa/Użytkownik" }, - "lowKdfIterations": { - "message": "Niska liczba iteracji KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Zaktualizuj ustawienia szyfrowania, aby spełnić nowe zalecenia bezpieczeństwa i poprawić ochronę konta." - }, "kdfSettingsChangeLogoutWarning": { "message": "Kontynuowanie spowoduje wylogowanie ze wszystkich aktywnych sesji. Będzie trzeba zalogować się ponownie i wykonać logowanie dwuetapowe, jeśli jest włączone. Zalecamy wyeksportowanie sejfu przed zmianą ustawień szyfrowania, aby zapobiec utracie danych." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Niska liczba iteracji KDF. Zwiększ liczbę iteracji, aby zwiększyć bezpieczeństwo Twojego konta." - }, - "changeKDFSettings": { - "message": "Zmień ustawienia KDF" - }, "secureYourInfrastructure": { "message": "Zabezpiecz swoją infrastrukturę" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index b2a2924b28b..c7e782c32eb 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Criar item de \"login\"" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Aplicativos críticos ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membros notificados ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Não há itens para listar." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Você não tem permissão para visualizar todos os itens desta coleção." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organização está desabilitada." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "As organizações suspensas não podem ser acessadas. Entre em contato com o proprietário da organização para obter assistência." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Contas de serviço não podem ser criadas em organizações suspensas. Entre em contato com o proprietário da organização para obter assistência." }, - "disabledOrganizationFilterError": { - "message": "Itens em Organizações Desativadas não podem ser acessados. Entre em contato com o proprietário da sua Organização para obter ajuda." - }, "licenseIsExpired": { "message": "A licença está expirada." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Identificador SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Forneça esse ID aos seus membros para logar com SSO. Para ignorar essa etapa, configure ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Desvincular SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupo/Usuário" }, - "lowKdfIterations": { - "message": "Iterações KDF baixas" - }, - "updateLowKdfIterationsDesc": { - "message": "Atualize suas configurações de criptografia para atender às novas recomendações de segurança e melhorar a proteção da conta." - }, "kdfSettingsChangeLogoutWarning": { "message": "O processo desconectará você de todas as sessões ativas. Você precisará iniciar a sessão novamente e concluir o login em duas etapas, se houver. Recomendamos exportar seu cofre antes de alterar suas configurações de criptografia para evitar perda de dados." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Iterações baixas do KDF. Aumente as suas iterações para melhorar a segurança da sua conta." - }, - "changeKDFSettings": { - "message": "Atualizar as definições do KDF" - }, "secureYourInfrastructure": { "message": "Proteja sua infraestrutura" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 6f8e4d9c11c..ff2b97eb1d7 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Criar nova credencial" }, - "criticalApplicationsActivityDescription": { - "message": "Depois de marcar as aplicações como críticas, estas serão apresentadas aqui." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Depois de marcar as aplicações como críticas, estas serão apresentadas aqui" + }, + "viewAtRiskMembers": { + "message": "Ver membros em risco" + }, + "viewAtRiskApplications": { + "message": "Ver aplicações em risco" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ de $TOTAL$ aplicações críticas estão em risco devido a palavras-passe em risco", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Aplicações críticas ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ aplicações em risco", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membros notificados ($COUNT$)", "placeholders": { @@ -137,7 +165,7 @@ "membersAtRiskActivityDescription": { "message": "Membros com acesso de edição a itens em risco para aplicações críticas" }, - "membersAtRisk": { + "membersAtRiskCount": { "message": "$COUNT$ membros em risco", "placeholders": { "count": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Não existem itens para listar." }, + "noItemsInTrash": { + "message": "Nenhum item no lixo" + }, + "noItemsInTrashDesc": { + "message": "Os itens que eliminar aparecerão aqui e serão permanentemente eliminados após 30 dias" + }, + "noItemsInVault": { + "message": "Nenhum item no cofre" + }, + "emptyVaultDescription": { + "message": "O cofre protege mais do que apenas as suas palavras-passe. Guarde aqui credenciais, IDs, cartões e notas de forma segura." + }, + "emptyFavorites": { + "message": "Não adicionou nenhum item aos favoritos" + }, + "emptyFavoritesDesc": { + "message": "Adicione itens utilizados frequentemente aos favoritos para um acesso rápido." + }, + "noSearchResults": { + "message": "Não foram apresentados resultados de pesquisa" + }, + "clearFiltersOrTryAnother": { + "message": "Limpe os filtros ou tente outro termo de pesquisa" + }, "noPermissionToViewAllCollectionItems": { "message": "Não tem permissão para ver todos os itens desta coleção." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organização suspensa" }, + "organizationIsSuspended": { + "message": "A organização está suspensa" + }, + "organizationIsSuspendedDesc": { + "message": "Não é possível aceder aos itens de organizações suspensas. Contacte o proprietário da organização para obter assistência." + }, "secretsAccessSuspended": { "message": "Não é possível aceder a organizações suspensas. Por favor, contacte o proprietário da organização para obter assistência." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "As contas de serviço não podem ser criadas em organizações suspensas. Por favor, contacte o proprietário da organização para obter assistência." }, - "disabledOrganizationFilterError": { - "message": "Não é possível aceder aos itens de organizações suspensas. Contacte o proprietário da organização para obter assistência." - }, "licenseIsExpired": { "message": "A licença expirou." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Identificador SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Forneça este ID aos seus membros para iniciarem sessão com o SSO. Para ignorar este passo, configure a ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Forneça este ID aos seus membros para iniciarem sessão com o SSO. Os membros podem ignorar a introdução deste identificador durante o SSO se estiver configurado um domínio reivindicado. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Saiba mais", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Desvincular SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupo/Utilizador" }, - "lowKdfIterations": { - "message": "Iterações KDF baixas" - }, - "updateLowKdfIterationsDesc": { - "message": "Atualize as suas definições de encriptação para cumprir as novas recomendações de segurança e melhorar a proteção da conta." - }, "kdfSettingsChangeLogoutWarning": { "message": "Ao prosseguir, sairá de todas as sessões ativas. Terá de voltar a iniciar sessão e concluir a verificação de dois passos, caso exista. Recomendamos que exporte o seu cofre antes de alterar as definições de encriptação para evitar a perda de dados." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Falha ao guardar a integração. Por favor, tente novamente mais tarde." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Precisa de ser o proprietário da organização para executar esta ação." + }, "failedToDeleteIntegration": { "message": "Falha ao eliminar a integração. Por favor, tente novamente mais tarde." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Baixa quantidade de iterações do KDF. Aumente as suas iterações para melhorar a segurança da sua conta." - }, - "changeKDFSettings": { - "message": "Alterar as definições do KDF" - }, "secureYourInfrastructure": { "message": "Proteja a sua infraestrutura" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Procurar no arquivo" }, - "archive": { - "message": "Arquivar" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Nenhum item no arquivo" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 364e7f86e64..974250b3934 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Niciun articol de afișat." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organizație suspendată" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Articolele din organizațiile suspendate nu pot fi accesate. Contactați proprietarul organizației pentru asistență." - }, "licenseIsExpired": { "message": "Licența a expirat." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Deconectare SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 753b5745f36..f06fa1449b8 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Создать новый логин" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Критичные приложения ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Уведомленные участники ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Нет элементов для отображения." }, + "noItemsInTrash": { + "message": "Нет элементов в корзине" + }, + "noItemsInTrashDesc": { + "message": "Элементы, которые вы удаляете, появятся здесь и будут удалены навсегда через 30 дней" + }, + "noItemsInVault": { + "message": "В хранилище нет элементов" + }, + "emptyVaultDescription": { + "message": "Хранилище защищает не только ваши пароли. Логины, идентификаторы, карты и заметки в нем надежно защищены." + }, + "emptyFavorites": { + "message": "В избранном нет элементов" + }, + "emptyFavoritesDesc": { + "message": "Добавляйте часто используемые элементы в избранное для быстрого доступа." + }, + "noSearchResults": { + "message": "Поиск не дал результатов" + }, + "clearFiltersOrTryAnother": { + "message": "Очистите фильтры или попробуйте другой поисковый запрос" + }, "noPermissionToViewAllCollectionItems": { "message": "У вас нет разрешения на просмотр всех элементов этой коллекции." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Организация приостановлена" }, + "organizationIsSuspended": { + "message": "Организация приостановлена" + }, + "organizationIsSuspendedDesc": { + "message": "Доступ к элементам в отключенных организациях невозможен. Обратитесь за помощью к владельцу организации." + }, "secretsAccessSuspended": { "message": "Доступ к отключенным организациям невозможен. Обратитесь за помощью к владельцу организации." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Сервисные аккаунты не могут быть созданы в отключенных организациях. Обратитесь за помощью к владельцу организации." }, - "disabledOrganizationFilterError": { - "message": "Доступ к элементам в отключенных организациях невозможен. Обратитесь за помощью к владельцу организации." - }, "licenseIsExpired": { "message": "Срок действия лицензии истек." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO идентификатор" }, - "ssoIdentifierHintPartOne": { - "message": "Предоставьте этот ID пользователям для авторизации с помощью SSO. Чтобы обойти этот шаг, настройте ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Предоставьте этот ID своим пользователям для авторизации при помощи единого входа. Пользователи могут не вводить этот идентификатор во время единого входа, если настроен заявленный домен. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Подробнее", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Отключить SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Группа/Пользователь" }, - "lowKdfIterations": { - "message": "Низкие итерации KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Обновите настройки шифрования в соответствии с новыми рекомендациями по безопасности и улучшите защиту аккаунта." - }, "kdfSettingsChangeLogoutWarning": { "message": "При продолжении все активные сессии будут завершены. Вам потребуется авторизоваться повторно и выполнить двухэтапную аутентификацию, если она включена. Мы рекомендуем экспортировать хранилище перед изменением настроек шифрования, чтобы предотвратить потерю данных." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Не удалось сохранить интеграцию. Пожалуйста, повторите попытку позже." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Для выполнения этого действия вы должны быть владельцем организации." + }, "failedToDeleteIntegration": { "message": "Не удалось удалить интеграцию. Пожалуйста, повторите попытку позже." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Низкое количество итераций KDF. Увеличьте количество итераций, чтобы повысить защищенность вашего аккаунта." - }, - "changeKDFSettings": { - "message": "Изменить параметры KDF" - }, "secureYourInfrastructure": { "message": "Защитите вашу инфраструктуру" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Поиск в архиве" }, - "archive": { - "message": "Архив" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "В архиве нет элементов" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index b2b9714e766..062ea291fa1 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 396d37e157e..8bafa670e29 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Pridať novu položku s prihlásením" }, - "criticalApplicationsActivityDescription": { - "message": "Tu sa zobrazia aplikácie, ktoré označíte za kritické." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "Zobraziť ohrozených členov" + }, + "viewAtRiskApplications": { + "message": "Zobraziť ohrozené aplikácie" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ z $TOTAL$ kritických aplikácii je ohrozených nebezpečnými heslami", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritické aplikácie ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ aplikácií ohrozených", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Členovia s oprávnením upravovať ohrozené položky kritických aplikácii" }, - "membersAtRisk": { - "message": "$COUNT$ ohrozených členov", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Neexistujú žiadne položky na zobrazenie." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Nemáte povolenie pre zobrazenie všetkých položiek v tejto zbierke." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organizácia je vypnutá." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "K pozastaveným organizáciám nie je možné pristupovať. Požiadajte o pomoc vlastníka organizácie." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "V pozastavených organizáciám nie je možné vytvárať služobné kontá. Požiadajte o pomoc vlastníka organizácie." }, - "disabledOrganizationFilterError": { - "message": "K položkám vo vypnutej organizácii nie je možné pristupovať. Požiadajte o pomoc vlastníka organizácie." - }, "licenseIsExpired": { "message": "Licencia vypršala." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifikátor" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Zistiť viac", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Odpojiť SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Skupina/Používateľ" }, - "lowKdfIterations": { - "message": "Nízky počet iterácií KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Aktualizujte vaše nastavenie šifrovania aby ste boli v súlade s bezpečnostnými odporúčaniami a vylepšili si tak ochranu vášho konta." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Nepodarilo sa uložiť integráciu. Prosím skúste to neskôr." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Pre vykonanie tejto akcie musíte by vlastník organizácie." + }, "failedToDeleteIntegration": { "message": "Nepodarilo sa odstrániť integráciu. Prosím skúste to neskôr." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Nízky počet iterácií KDF. Pre zlepšenie bezpečnosti vášho konta, zvýšte počet iterácií." - }, - "changeKDFSettings": { - "message": "Zmeniť nastavenia KDF" - }, "secureYourInfrastructure": { "message": "Zabezpečte si infraštruktúru" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Prehľadať archív" }, - "archive": { - "message": "Archív" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Žiadne položky v archíve" diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index e54ef8bb4b6..17431aedecb 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Ni elementov za prikaz." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Nimate dovoljenja za ogled vseh elementov v tej zbirki." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "Licenca je potekla" }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Nizko število ponovitev KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Popravite svoje nastavitve šifriranja, da bodo v skladu z novimi priporočili za varnost, in izboljšajte zaščito svojega računa." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 8b8edaa1ec3..c3278674a56 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Kreirajte novu stavku za prijavu" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritične aplikacije ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Obavešteni članovi ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Nema stavki u listi." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index 68a440646a3..b500c6230e4 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Креирајте нову ставку за пријаву" }, - "criticalApplicationsActivityDescription": { - "message": "Апликације обележене као критичне, су приказане овде." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Критичне апликације ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Обавештени чланови ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Чланови са правом уређивања за угрожене ставке критичних апликација" }, - "membersAtRisk": { - "message": "Угрожени чланови: $COUNT$", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Нама ставке у листи." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Немате дозволу да видите све ставке у овој колекцији." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Организација је онемогућена." }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Суспендованим организацијама се не може приступити. За помоћ контактирајте власника своје организације." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Сервисни налози се не могу креирати у суспендованим организацијама. За помоћ контактирајте власника своје организације." }, - "disabledOrganizationFilterError": { - "message": "Није могуће приступити ставкама у онемогућене организације. Обратите се власнику организације за помоћ." - }, "licenseIsExpired": { "message": "Лиценца је истекла." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO идентификација" }, - "ssoIdentifierHintPartOne": { - "message": "Дајте овај ИД својим члановима да се пријаве са ССО. Да бисте заобишли овај корак, подесите ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Откачи SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Група/Корисник" }, - "lowKdfIterations": { - "message": "Ниска KDF понављања" - }, - "updateLowKdfIterationsDesc": { - "message": "Ажурирајте подешавања шифровања да бисте испунили нове безбедносне препоруке и побољшали заштиту налога." - }, "kdfSettingsChangeLogoutWarning": { "message": "Ако наставите, одјавићете се са свих активних сесија. Мораћете поново да се пријавите и завршитепријаве у два корака, ако имате. Препоручујемо да извезете трезор пре него што промените подешавања шифровања да бисте спречили губитак података." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Није успело сачувавање интеграције. Покушајте поново касније." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Ниске KDF итерације. Повећајте број понављања да бисте побољшали безбедност свог налога." - }, - "changeKDFSettings": { - "message": "Променити KDF подешавања" - }, "secureYourInfrastructure": { "message": "Обезбедите своју инфраструктуру" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Претражи архиву" }, - "archive": { - "message": "Архива" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Нема ставка у архиви" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 853e60c95bc..130d076eee6 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Skapa nytt inloggningsobjekt" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "När du markerar applikationer som kritiska så kommer de att visas här" + }, + "viewAtRiskMembers": { + "message": "Visa medlemmar i riskzonen" + }, + "viewAtRiskApplications": { + "message": "Visa applikationer i riskzonen" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ av $TOTAL$ kritiska applikationer är i riskzonen på grund av lösenord i riskzonen", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritiska applikationer ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applikationer i riskzonen", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Meddelade medlemmar ($COUNT$)", "placeholders": { @@ -135,10 +163,10 @@ "message": "Riskutsatta medlemmar" }, "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "message": "Medlemmar med redigeringsbehörighet till objekt i riskzonen för kritiska applikationer" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ medlemmar i riskzonen", "placeholders": { "count": { "content": "$1", @@ -782,11 +810,11 @@ "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "Ny textsändning", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "Ny filsändning", "description": "Header for new file send" }, "editItemHeaderLogin": { @@ -810,11 +838,11 @@ "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "Redigera textsändning", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "Redigera filsändning", "description": "Header for edit file send" }, "viewItemHeaderLogin": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Det finns inga objekt att visa." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Du har inte behörighet att se alla objekt i denna samling." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organisationen är inaktiverad" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Avstängda organisationer kan inte nås. Kontakta din organisationsägare för hjälp." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Servicekonton kan inte skapas i avstängda organisationer. Kontakta din organisationsägare för att få hjälp." }, - "disabledOrganizationFilterError": { - "message": "Det går inte att komma åt objekt i avstängda organisationer. Kontakta din organisationsägare för hjälp." - }, "licenseIsExpired": { "message": "Licensen har upphört att gälla." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO-identifierare" }, - "ssoIdentifierHintPartOne": { - "message": "Ge detta ID till dina medlemmar så att de kan logga in med SSO. För att kringgå detta steg, konfigurera ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Avlänka SSO" @@ -6729,7 +6788,7 @@ "message": "SSO inaktiverad" }, "emailMustLoginWithSso": { - "message": "$EMAIL$ must login with Single Sign-on", + "message": "$EMAIL$ måste logga in med Single Sign-on", "placeholders": { "email": { "content": "$1", @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grupp/Användare" }, - "lowKdfIterations": { - "message": "Låga KDF-iterationer" - }, - "updateLowKdfIterationsDesc": { - "message": "Uppdatera dina krypteringsinställningar så att de uppfyller nya säkerhetsrekommendationer och förbättrar kontots skydd." - }, "kdfSettingsChangeLogoutWarning": { "message": "Om du fortsätter loggas du ut från alla aktiva sessioner. Du måste logga in igen och genomföra tvåstegsinloggning, om sådan finns. Vi rekommenderar att du exporterar ditt valv innan du ändrar dina krypteringsinställningar för att förhindra dataförlust." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Misslyckades med att spara integration. Försök igen senare." }, + "mustBeOrgOwnerToPerformAction": { + "message": "Du måste vara organisationens ägare för att utföra denna åtgärd." + }, "failedToDeleteIntegration": { "message": "Misslyckades med att ta bort integrationen. Försök igen senare." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Låga KDF-iterationer. Öka dina iterationer för att förbättra säkerheten för ditt konto." - }, - "changeKDFSettings": { - "message": "Ändra KDF-inställningar" - }, "secureYourInfrastructure": { "message": "Säkra din infrastruktur" }, @@ -11022,16 +11072,21 @@ "message": "Gratisorganisationer kan ha upp till 2 samlingar. Uppgradera till en betald plan för att lägga till fler samlingar." }, "searchArchive": { - "message": "Search archive" + "message": "Sök i arkiv" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Inga objekt i arkivet" }, "archivedItemsDescription": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Arkiverade objekt kommer att visas här och kommer att uteslutas från allmänna sökresultat och förslag för autofyll." }, "businessUnit": { "message": "Affärsenhet" @@ -11370,7 +11425,7 @@ "message": "Additional storage GB" }, "additionalServiceAccountsV2": { - "message": "Additional machine accounts" + "message": "Ytterligare maskinkonton" }, "secretsManagerSeats": { "message": "Secrets Manager seats" @@ -11391,10 +11446,10 @@ "message": "Complete online security" }, "planDescFamiliesV2": { - "message": "Premium security for your family" + "message": "Premiumsäkerhet för din familj" }, "planDescFreeV2": { - "message": "Share with $COUNT$ other user", + "message": "Dela med $COUNT$ annan användare", "placeholders": { "count": { "content": "$1", @@ -11403,37 +11458,37 @@ } }, "planDescEnterpriseV2": { - "message": "Advanced capabilities for any organization" + "message": "Avancerade förmågor för alla organisationer" }, "planNameCustom": { - "message": "Custom plan" + "message": "Anpassad plan" }, "planDescCustom": { - "message": "Bitwarden scales with businesses of all sizes to secure passwords and sensitive information. If you're part of a large enterprise, contact sales to request a quote." + "message": "Bitwarden skalar med företag i alla storlekar för att säkra lösenord och känslig information. Om du är en del av ett stort företag, kontakta vår försäljningsavdelning för att begära en offert." }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Inbyggd authenticator" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Intrångsmonitorering" }, "andMoreFeatures": { "message": "Och mer!" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Säker fillagring" }, "familiesUnlimitedSharing": { - "message": "Unlimited sharing - choose who sees what" + "message": "Obegränsad delning - välj vem som ser vad" }, "familiesUnlimitedCollections": { - "message": "Unlimited family collections" + "message": "Obegränsade familjesamlingar" }, "familiesSharedStorage": { - "message": "Shared storage for important family info" + "message": "Delad lagring för viktig familjeinformation" }, "limitedUsersV2": { - "message": "Up to $COUNT$ members", + "message": "Upp till $COUNT$ medlemmar", "placeholders": { "count": { "content": "$1", @@ -11442,7 +11497,7 @@ } }, "limitedCollectionsV2": { - "message": "Up to $COUNT$ collections", + "message": "Upp till $COUNT$ samlingar", "placeholders": { "count": { "content": "$1", @@ -11451,13 +11506,13 @@ } }, "alwaysFree": { - "message": "Always free" + "message": "Alltid gratis" }, "twoSecretsIncluded": { - "message": "2 secrets" + "message": "2 hemligheter" }, "projectsIncludedV2": { - "message": "$COUNT$ project(s)", + "message": "$COUNT$ projekt", "placeholders": { "count": { "content": "$1", @@ -11466,13 +11521,13 @@ } }, "secureItemSharing": { - "message": "Secure item sharing" + "message": "Säker objektdelning" }, "scimSupport": { - "message": "SCIM support" + "message": "SCIM-stöd" }, "includedMachineAccountsV2": { - "message": "$COUNT$ machine accounts", + "message": "$COUNT$ maskinkonton", "placeholders": { "count": { "content": "$1", @@ -11481,21 +11536,21 @@ } }, "enterpriseSecurityPolicies": { - "message": "Enterprise security policies" + "message": "Säkerhetspolicyer för företag" }, "selfHostOption": { "message": "Self-host option" }, "complimentaryFamiliesPlan": { - "message": "Complimentary families plan for all users" + "message": "Kostnadsfri familjeplan för alla användare" }, "strengthenCybersecurity": { - "message": "Strengthen cybersecurity" + "message": "Förbättrad cybersäkerhet" }, "boostProductivity": { - "message": "Boost productivity" + "message": "Maximera produktiviteten" }, "seamlessIntegration": { - "message": "Seamless integration" + "message": "Sömlös integration" } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index dc3e32726d4..3534de7e547 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "புதிய உள்நுழைவு உருப்படியை உருவாக்கவும்" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "முக்கியமான பயன்பாடுகள் ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "அறிவிக்கப்பட்ட உறுப்பினர்கள் ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "பட்டியலிட உருப்படிகள் எதுவும் இல்லை." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "இந்தத் தொகுப்பிலுள்ள எல்லா உருப்படிகளையும் பார்க்க உங்களுக்கு அனுமதி இல்லை." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "நிறுவனம் இடைநிறுத்தப்பட்டது" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "இடைநிறுத்தப்பட்ட நிறுவனங்களை அணுக முடியாது. உதவிக்கு உங்கள் நிறுவன உரிமையாளரைத் தொடர்புகொள்ளவும்." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "இடைநிறுத்தப்பட்ட நிறுவனங்களில் சேவை கணக்குகளை உருவாக்க முடியாது. உதவிக்கு உங்கள் நிறுவன உரிமையாளரைத் தொடர்புகொள்ளவும்." }, - "disabledOrganizationFilterError": { - "message": "இடைநிறுத்தப்பட்ட நிறுவனங்களில் உள்ள பொருட்களை அணுக முடியாது. உதவிக்கு உங்கள் நிறுவன உரிமையாளரைத் தொடர்புகொள்ளவும்." - }, "licenseIsExpired": { "message": "உரிமம் காலாவதியாகிவிட்டது." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO அடையாளங்காட்டி" }, - "ssoIdentifierHintPartOne": { - "message": "SSO உடன் உள்நுழைய இந்த ஐடியை உங்கள் உறுப்பினர்களுக்கு வழங்கவும். இந்த படியைத் தவிர்க்க, அமைக்கவும் ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO இணைப்பை நீக்கு" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "குழு/பயனர்" }, - "lowKdfIterations": { - "message": "குறைந்த KDF இட்டரேஷன்ஸ்" - }, - "updateLowKdfIterationsDesc": { - "message": "புதிய பாதுகாப்புப் பரிந்துரைகளை பூர்த்தி செய்யவும் மற்றும் கணக்கு பாதுகாப்பை மேம்படுத்தவும் உங்கள் குறியாக்க அமைப்புகளைப் புதுப்பிக்கவும்." - }, "kdfSettingsChangeLogoutWarning": { "message": "தொடர்வது அனைத்து செயலில் உள்ள அமர்வுகளிலிருந்தும் உங்களை வெளியேற்றும். நீங்கள் மீண்டும் உள்நுழைய வேண்டும் மற்றும் இரண்டு-படி உள்நுழைவை முடிக்க வேண்டும், ஏதேனும் இருந்தால். தரவு இழப்பைத் தடுக்க உங்கள் குறியாக்க அமைப்புகளை மாற்றுவதற்கு முன் உங்கள் பெட்டகத்தை ஏற்றுமதி செய்யுமாறு பரிந்துரைக்கிறோம்." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "ஒருங்கிணைப்பைச் சேமிக்கத் தவறிவிட்டது. பின்னர் மீண்டும் முயற்சிக்கவும்." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "குறைந்த KDF இட்டரேஷன்கள். உங்கள் கணக்கின் பாதுகாப்பை மேம்படுத்த, உங்கள் இட்டரேஷன்களை அதிகரிக்கவும்." - }, - "changeKDFSettings": { - "message": "KDF அமைப்புகளை மாற்றவும்" - }, "secureYourInfrastructure": { "message": "உங்கள் கட்டமைப்பைப் பாதுகாக்கவும்" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 526b7567d99..fa4c124416d 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "There are no items to list." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 0f9c3bea24d..717c78cb3ca 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Create new login item" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "ไม่มีรายการการสำหรับแสดง" }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "You do not have permission to view all items in this collection." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, - "disabledOrganizationFilterError": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." - }, "licenseIsExpired": { "message": "License is expired." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO identifier" }, - "ssoIdentifierHintPartOne": { - "message": "Provide this ID to your members to login with SSO. To bypass this step, set up ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Unlink SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Group/User" }, - "lowKdfIterations": { - "message": "Low KDF Iterations" - }, - "updateLowKdfIterationsDesc": { - "message": "Update your encryption settings to meet new security recommendations and improve account protection." - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." - }, - "changeKDFSettings": { - "message": "Change KDF settings" - }, "secureYourInfrastructure": { "message": "Secure your infrastructure" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index f36a336a4d1..5a52f872f57 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Yeni hesap kaydı oluştur" }, - "criticalApplicationsActivityDescription": { - "message": "Uygulamaları kritik olarak işaretlediğinizde, bunlar burada görüntülenir." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Kritik uygulamalar ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Bildirilen üyeler ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Kritik uygulamalar için risk altındaki kayıtlara düzenleme erişimi olan üyeler" }, - "membersAtRisk": { - "message": "$COUNT$ üye risk altında", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Listelenecek kayıt yok." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Bu koleksiyondaki tüm kayıtları görmek için izniniz yok." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Kuruluş askıya alındı" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Askıya alınan kuruluşlara erişilemez. Lütfen yardım için kuruluşunuzun sahibiyle iletişime geçin." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Askıya alınan kuruluşlarda hizmet hesapları oluşturulamaz. Lütfen yardım için kuruluşunuzun sahibiyle iletişime geçin." }, - "disabledOrganizationFilterError": { - "message": "Askıya alınmış kuruluşlardaki kayıtlara erişilemez. Destek almak için kuruluş sahibinizle iletişime geçin." - }, "licenseIsExpired": { "message": "Lisans süresi doldu." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO tanımlayıcı" }, - "ssoIdentifierHintPartOne": { - "message": "SSO ile giriş yapmaları için bu kimliği üyelerinize sağlayın. Bu adımı atlamak için kurulum yapın ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "SSO bağlantısını kes" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Grup/Kullanıcı" }, - "lowKdfIterations": { - "message": "Düşük KDF iterasyonu" - }, - "updateLowKdfIterationsDesc": { - "message": "Yeni güvenlik önerilerini karşılamak ve hesap korumasını iyileştirmek için şifreleme ayarlarınızı güncelleyin." - }, "kdfSettingsChangeLogoutWarning": { "message": "Devam ettiğinizde tüm aktif oturumlardan çıkış yapacaksınız. Tekrar oturum açmanız ve varsa iki aşamalı oturum açma işlemini tamamlamanız gerekecektir. Veri kaybını önlemek için şifreleme ayarlarınızı değiştirmeden önce kasayı dışa aktarmanızı öneririz." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Entegrasyon kaydedilemedi. Lütfen daha sonra tekrar deneyin." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Entegrasyon silinemedi. Lütfen daha sonra tekrar deneyin." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Düşük KDF iterasyonları. Hesabınızın güvenliğini artırmak için iterasyonları artırın." - }, - "changeKDFSettings": { - "message": "KDF ayarlarını değiştir" - }, "secureYourInfrastructure": { "message": "Altyapınızı güvence altına alın" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Arşivde ara" }, - "archive": { - "message": "Arşiv" + "archiveNoun": { + "message": "Arşiv", + "description": "Noun" + }, + "archiveVerb": { + "message": "Arşivle", + "description": "Verb" }, "noItemsInArchive": { "message": "Arşivde kayıt yok" diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index f68cf42627d..e1aa1169f43 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Створити новий запис" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Критичні програми ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Сповіщення учасників ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Немає записів." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "У вас немає повноважень на перегляд усіх записів у цій збірці." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Організацію призупинено" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "До призупинених організацій не можна отримати доступ. Зверніться до власника вашої організації для отримання допомоги." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "У призупинених організаціях не можна створювати службові облікові записи. Зверніться до власника вашої організації для отримання допомоги." }, - "disabledOrganizationFilterError": { - "message": "Записи у призупинених організаціях недоступні. Зверніться до власника вашої організації для отримання допомоги." - }, "licenseIsExpired": { "message": "Термін дії ліцензії завершився." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO-ідентифікатор" }, - "ssoIdentifierHintPartOne": { - "message": "Надайте цей ID своїм учасникам для входу з використанням SSO. Щоб обійти цей крок, налаштуйте ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Від'єднати SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Група/Користувач" }, - "lowKdfIterations": { - "message": "Низьке значення ітерацій KDF" - }, - "updateLowKdfIterationsDesc": { - "message": "Оновіть свої налаштування шифрування згідно з новими рекомендаціями щодо безпеки для вдосконалення захисту облікового запису." - }, "kdfSettingsChangeLogoutWarning": { "message": "Продовжуючи, ви вийдете з усіх активних сеансів. Необхідно буде повторно виконати вхід і пройти двоетапну перевірку, якщо вона увімкнена. Перед зміною налаштувань шифрування ми рекомендуємо експортувати ваше сховище, щоб запобігти можливій втраті даних." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Низьке значення ітерацій KDF. Збільште значення для посилення безпеки свого облікового запису." - }, - "changeKDFSettings": { - "message": "Змінити налаштування KDF" - }, "secureYourInfrastructure": { "message": "Захистіть свою інфраструктуру" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 9a87b3e2c6c..998fcf560e2 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "Tạo mục đăng nhập mới" }, - "criticalApplicationsActivityDescription": { - "message": "Khi bạn đánh dấu các ứng dụng là quan trọng, chúng sẽ hiển thị tại đây." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "Ứng dụng quan trọng ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Các thành viên đã được thông báo ($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Thành viên có quyền chỉnh sửa đối với các mục có nguy cơ cho các ứng dụng quan trọng" }, - "membersAtRisk": { - "message": "$COUNT$ thành viên có nguy cơ", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "Chưa có mục nào." }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "Bạn không có quyền xem tất cả mục trong bộ sưu tập này." }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "Tổ chức đã bị tạm ngưng" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "Không thể truy cập các tổ chức đã bị tạm ngưng. Vui lòng liên hệ với chủ sở hữu tổ chức của bạn để được hỗ trợ." }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "Không thể tạo tài khoản dịch vụ trong các tổ chức đã bị tạm ngưng. Vui lòng liên hệ với chủ sở hữu tổ chức của bạn để được hỗ trợ." }, - "disabledOrganizationFilterError": { - "message": "Không thể truy cập các mục trong tổ chức đã bị tạm ngưng. Vui lòng liên hệ với chủ sở hữu tổ chức của bạn để được hỗ trợ." - }, "licenseIsExpired": { "message": "Giấy phép đã hết hạn." }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "Mã định danh SSO" }, - "ssoIdentifierHintPartOne": { - "message": "Cung cấp ID này cho các thành viên của bạn để đăng nhập bằng SSO. Để bỏ qua bước này, hãy thiết lập ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "Hủy liên kết SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "Nhóm/Người dùng" }, - "lowKdfIterations": { - "message": "Số vòng lặp KDF thấp" - }, - "updateLowKdfIterationsDesc": { - "message": "Cập nhật cài đặt mã hóa của bạn để đáp ứng các khuyến nghị bảo mật mới và cải thiện bảo vệ tài khoản." - }, "kdfSettingsChangeLogoutWarning": { "message": "Tiếp tục sẽ đăng xuất bạn khỏi tất cả các phiên hoạt động. Bạn sẽ cần đăng nhập lại và hoàn tất đăng nhập hai bước (nếu có). Chúng tôi khuyến nghị xuất kho mật khẩu của bạn trước khi thay đổi cài đặt mã hóa để tránh mất dữ liệu." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Không thể lưu tích hợp. Vui lòng thử lại sau." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Không thể xóa tích hợp. Vui lòng thử lại sau." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "Số vòng lặp KDF thấp. Tăng số vòng lặp để cải thiện bảo mật cho tài khoản của bạn." - }, - "changeKDFSettings": { - "message": "Thay đổi cài đặt KDF" - }, "secureYourInfrastructure": { "message": "Bảo vệ hạ tầng của bạn" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Tìm kiếm kho lưu trữ" }, - "archive": { - "message": "Lưu trữ" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "Không có mục nào trong kho lưu trữ" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 38c585142df..e9b81cd5a07 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "创建新的登录项目" }, - "criticalApplicationsActivityDescription": { - "message": "您将应用程序标记为关键后,它们将显示在这里。" + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "您将应用程序标记为关键后,它们将显示在这里" + }, + "viewAtRiskMembers": { + "message": "查看存在风险的成员" + }, + "viewAtRiskApplications": { + "message": "查看存在风险的应用程序" + }, + "criticalApplicationsAreAtRisk": { + "message": "总计 $TOTAL$ 个中的 $COUNT$ 个关键应用程序因密码风险而存在风险", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "关键应用程序 ($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ 个应用程序存在风险", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "已通知的成员 ($COUNT$)", "placeholders": { @@ -137,7 +165,7 @@ "membersAtRiskActivityDescription": { "message": "对关键应用程序中存在风险的项目具有编辑权限的成员" }, - "membersAtRisk": { + "membersAtRiskCount": { "message": "$COUNT$ 个成员存在风险", "placeholders": { "count": { @@ -959,7 +987,7 @@ "message": "卡号" }, "copyFieldCipherName": { - "message": "复制 $CIPHERNAME$ 的 $FIELD$", + "message": "复制 $CIPHERNAME$ 中的 $FIELD$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "没有可列出的项目。" }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "密码库中没有项目" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "您没有查看此集合中的所有项目的权限。" }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "组织已暂停" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "无法访问已暂停的组织。请联系您的组织所有者寻求帮助。" }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "无法在已暂停的组织中创建服务账户。请联系您的组织所有者寻求帮助。" }, - "disabledOrganizationFilterError": { - "message": "无法访问已暂停组织中的项目。请联系您的组织所有者寻求帮助。" - }, "licenseIsExpired": { "message": "授权已过期" }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO 标识符" }, - "ssoIdentifierHintPartOne": { - "message": "将此 ID 提供给您的成员以供 SSO 登录使用。要绕过此步骤,请设置 ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "取消链接 SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "群组/用户" }, - "lowKdfIterations": { - "message": "较低的 KDF 迭代" - }, - "updateLowKdfIterationsDesc": { - "message": "更新您的加密设置以满足新的安全建议以及增强账户保护。" - }, "kdfSettingsChangeLogoutWarning": { "message": "接下来将会注销您所有的活动会话。您需要重新登录并完成两步登录(如果有)。我们建议您在更改加密设置前导出密码库,以防止数据丢失。" }, @@ -8760,7 +8813,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "满分 $TOTAL$", + "message": "总计 $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "保存集成失败。请稍后再试。" }, + "mustBeOrgOwnerToPerformAction": { + "message": "您必须是组织所有者才能执行此操作。" + }, "failedToDeleteIntegration": { "message": "删除集成失败。请稍后再试。" }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "KDF 迭代低。请增加迭代次数以提高您账户的安全性。" - }, - "changeKDFSettings": { - "message": "更改 KDF 设置" - }, "secureYourInfrastructure": { "message": "保护您的基础设施" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "搜索归档" }, - "archive": { - "message": "归档" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "归档中没有项目" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 4f9a78c2c02..78f1665121a 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -59,8 +59,27 @@ "createNewLoginItem": { "message": "新增登入項目" }, - "criticalApplicationsActivityDescription": { - "message": "Once you mark applications critical, they will display here." + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { + "message": "Once you mark applications critical, they will display here" + }, + "viewAtRiskMembers": { + "message": "View at-risk members" + }, + "viewAtRiskApplications": { + "message": "View at-risk applications" + }, + "criticalApplicationsAreAtRisk": { + "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } }, "criticalApplicationsWithCount": { "message": "重要應用程式($COUNT$)", @@ -80,6 +99,15 @@ } } }, + "countOfApplicationsAtRisk": { + "message": "$COUNT$ applications at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "已被通知的成員($COUNT$)", "placeholders": { @@ -137,8 +165,8 @@ "membersAtRiskActivityDescription": { "message": "Members with edit access to at-risk items for critical applications" }, - "membersAtRisk": { - "message": "$COUNT$ members at risk", + "membersAtRiskCount": { + "message": "$COUNT$ members at-risk", "placeholders": { "count": { "content": "$1", @@ -1480,6 +1508,30 @@ "noItemsInList": { "message": "沒有可列出的項目。" }, + "noItemsInTrash": { + "message": "No items in trash" + }, + "noItemsInTrashDesc": { + "message": "Items you delete will appear here and be permanently deleted after 30 days" + }, + "noItemsInVault": { + "message": "No items in the vault" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, + "emptyFavorites": { + "message": "You haven't favorited any items" + }, + "emptyFavoritesDesc": { + "message": "Add frequently used items to favorites for quick access." + }, + "noSearchResults": { + "message": "No search results returned" + }, + "clearFiltersOrTryAnother": { + "message": "Clear filters or try another search term" + }, "noPermissionToViewAllCollectionItems": { "message": "您沒有檢視此集合中所有項目的權限。" }, @@ -4772,6 +4824,12 @@ "organizationIsDisabled": { "message": "組織已停用" }, + "organizationIsSuspended": { + "message": "Organization is suspended" + }, + "organizationIsSuspendedDesc": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, "secretsAccessSuspended": { "message": "無法存取已停用的組織。請聯絡您組織的擁有者以獲取協助。" }, @@ -4784,9 +4842,6 @@ "serviceAccountsCannotCreate": { "message": "無法在已停用組織中新增服務帳戶。請聯絡您組織的擁有者以獲取協助。" }, - "disabledOrganizationFilterError": { - "message": "無法存取已停用組織中的項目。請聯絡您組織的擁有者以獲取協助。" - }, "licenseIsExpired": { "message": "授權已逾期。" }, @@ -5160,9 +5215,13 @@ "ssoIdentifier": { "message": "SSO 識別" }, - "ssoIdentifierHintPartOne": { - "message": "將此 ID 提供給您的成員以使用 SSO 登入。若要繞過此步驟,請設定 ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Provide this ID to your members to login with SSO. To bypass this step, set up Domain verification'" + "ssoIdentifierHint": { + "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" + }, + "claimedDomainsLearnMore": { + "message": "Learn more", + "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { "message": "取消連結 SSO" @@ -8376,12 +8435,6 @@ "groupSlashUser": { "message": "群組/使用者" }, - "lowKdfIterations": { - "message": "較低的 KDF 迭代次數" - }, - "updateLowKdfIterationsDesc": { - "message": "更新您的加密設定以滿足新的安全建議以及提升帳號保護。" - }, "kdfSettingsChangeLogoutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, @@ -9755,6 +9808,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "mustBeOrgOwnerToPerformAction": { + "message": "You must be the organization owner to perform this action." + }, "failedToDeleteIntegration": { "message": "Failed to delete integration. Please try again later." }, @@ -9996,12 +10052,6 @@ } } }, - "lowKDFIterationsBanner": { - "message": "低 KDF 迭代。增加迭代次數以提高帳號的安全性。" - }, - "changeKDFSettings": { - "message": "變更 KDF 設定" - }, "secureYourInfrastructure": { "message": "保護你的基礎設施" }, @@ -11024,8 +11074,13 @@ "searchArchive": { "message": "Search archive" }, - "archive": { - "message": "Archive" + "archiveNoun": { + "message": "Archive", + "description": "Noun" + }, + "archiveVerb": { + "message": "Archive", + "description": "Verb" }, "noItemsInArchive": { "message": "No items in archive" From 4313dd2ecb9c11ecd761bc35f0db96678ce0f75d Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Thu, 2 Oct 2025 08:55:04 -0400 Subject: [PATCH 33/83] feat(SendAccess): [Auth/PM-22661] SendTokenService + SDK Integration (#16007) * PM-22661 - Start bringing in code from original PR * PM-22661 - SendTokenService - implement and test hash send password * PM-22661 - Starting to pull in SDK state to SendTokenService * PM-22661 - WIP on default send token service * PM-22661 - Build out TS helpers for TryGetSendAccessTokenError * PM-22661 - WIP * PM-22661 - Decent progress on getting _tryGetSendAccessToken wired up * PM-22661 - Finish service implementation (TODO: test) * PM-22661 - DefaultSendTokenService - clear expired tokens * PM-22661 - SendTokenService - tests for tryGetSendAccessToken$ * PM-22661 - DefaultSendTokenService - more tests. * PM-22661 - Refactor to create domain facing type for send access creds so we can internally map to SDK models instead of exposing them. * PM-22661 - DefaultSendTokenService tests - finish testing error scenarios * PM-22661 - SendAccessToken - add threshold to expired check to prevent tokens from expiring in flight * PM-22661 - clean up docs and add invalidateSendAccessToken * PM-22661 - Add SendAccessToken tests * PM-22661 - Build out barrel files and provide send token service in jslib-services. * PM-22661 - Improve credential validation and test the scenarios * PM-22661 - Add handling for otp_generation_failed * PM-22661 - npm i sdk version 0.2.0-main.298 which has send access client stuff * PM-22661 - Bump to latest sdk changes for send access for testing. * PM-22661 - fix comment to be accurate * PM-22661 - DefaultSendTokenService - hashSendPassword - to fix compile time error with passing a Uint8Array to Utils.fromBufferToB64, add new overloads to Utils.fromBufferToB64 to handle ArrayBuffer and ArrayBufferView (to allow for Uint8Arrays). Then, test new scenarios to ensure feature parity with old fromBufferToB64 method. * PM-22661 - Utils.fromBufferToB64 - remove overloads so ordering doesn't break test spies. * PM-22661 - utils.fromBufferToB64 - re-add overloads to see effects on tests * PM-22661 - revert utils changes as they will be done in a separate PR. * PM-22661 - SendTokenService tests - test invalidateSendAccessToken * PM-22661 - DefaultSendTokenService - add some storage layer tests * PM-22661 - Per PR feedback fix comment * PM-22661 - Per PR feedback, optimize writes to state for send access tokens with shouldUpdate. * PM-22661 - Per PR feedback, update clear to be immutable vs delete (mutation) based. * PM-22661 - Per PR feedback, re-add should update for clear method. * PM-22661 - Update libs/common/src/auth/send-access/services/default-send-token.service.ts Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com> * PM-22661 - Update libs/common/src/auth/send-access/services/default-send-token.service.ts Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com> * PM-22661 - Update libs/common/src/auth/send-access/services/default-send-token.service.ts Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com> --------- Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com> --- .../src/services/jslib-services.module.ts | 6 + .../auth/send-access/abstractions/index.ts | 1 + .../abstractions/send-token.service.ts | 57 ++ libs/common/src/auth/send-access/index.ts | 4 + .../src/auth/send-access/models/index.ts | 1 + .../models/send-access-token.spec.ts | 75 ++ .../send-access/models/send-access-token.ts | 46 ++ .../default-send-token.service.spec.ts | 678 ++++++++++++++++++ .../services/default-send-token.service.ts | 316 ++++++++ .../src/auth/send-access/services/index.ts | 1 + .../services/send-access-token-dict.state.ts | 15 + .../types/get-send-access-token-error.type.ts | 12 + .../src/auth/send-access/types/index.ts | 7 + .../types/invalid-grant-errors.type.ts | 62 ++ .../types/invalid-request-errors.type.ts | 62 ++ .../send-access-domain-credentials.type.ts | 11 + .../types/send-hashed-password-b64.type.ts | 3 + .../auth/send-access/types/send-otp.type.ts | 3 + .../try-get-send-access-token-error.type.ts | 7 + libs/state/src/core/state-definitions.ts | 1 + package-lock.json | 8 +- package.json | 2 +- 22 files changed, 1373 insertions(+), 5 deletions(-) create mode 100644 libs/common/src/auth/send-access/abstractions/index.ts create mode 100644 libs/common/src/auth/send-access/abstractions/send-token.service.ts create mode 100644 libs/common/src/auth/send-access/index.ts create mode 100644 libs/common/src/auth/send-access/models/index.ts create mode 100644 libs/common/src/auth/send-access/models/send-access-token.spec.ts create mode 100644 libs/common/src/auth/send-access/models/send-access-token.ts create mode 100644 libs/common/src/auth/send-access/services/default-send-token.service.spec.ts create mode 100644 libs/common/src/auth/send-access/services/default-send-token.service.ts create mode 100644 libs/common/src/auth/send-access/services/index.ts create mode 100644 libs/common/src/auth/send-access/services/send-access-token-dict.state.ts create mode 100644 libs/common/src/auth/send-access/types/get-send-access-token-error.type.ts create mode 100644 libs/common/src/auth/send-access/types/index.ts create mode 100644 libs/common/src/auth/send-access/types/invalid-grant-errors.type.ts create mode 100644 libs/common/src/auth/send-access/types/invalid-request-errors.type.ts create mode 100644 libs/common/src/auth/send-access/types/send-access-domain-credentials.type.ts create mode 100644 libs/common/src/auth/send-access/types/send-hashed-password-b64.type.ts create mode 100644 libs/common/src/auth/send-access/types/send-otp.type.ts create mode 100644 libs/common/src/auth/send-access/types/try-get-send-access-token-error.type.ts diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 3304c54a86f..df135dcc0ef 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -104,6 +104,7 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@ import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction"; import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; +import { SendTokenService, DefaultSendTokenService } from "@bitwarden/common/auth/send-access"; import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service"; @@ -1588,6 +1589,11 @@ const safeProviders: SafeProvider[] = [ MessageListener, ], }), + safeProvider({ + provide: SendTokenService, + useClass: DefaultSendTokenService, + deps: [GlobalStateProvider, SdkService, SendPasswordService], + }), safeProvider({ provide: EndUserNotificationService, useClass: DefaultEndUserNotificationService, diff --git a/libs/common/src/auth/send-access/abstractions/index.ts b/libs/common/src/auth/send-access/abstractions/index.ts new file mode 100644 index 00000000000..be8d6282020 --- /dev/null +++ b/libs/common/src/auth/send-access/abstractions/index.ts @@ -0,0 +1 @@ +export * from "./send-token.service"; diff --git a/libs/common/src/auth/send-access/abstractions/send-token.service.ts b/libs/common/src/auth/send-access/abstractions/send-token.service.ts new file mode 100644 index 00000000000..3ecdc101892 --- /dev/null +++ b/libs/common/src/auth/send-access/abstractions/send-token.service.ts @@ -0,0 +1,57 @@ +import { Observable } from "rxjs"; + +import { SendAccessToken } from "../models/send-access-token"; +import { GetSendAccessTokenError } from "../types/get-send-access-token-error.type"; +import { SendAccessDomainCredentials } from "../types/send-access-domain-credentials.type"; +import { SendHashedPasswordB64 } from "../types/send-hashed-password-b64.type"; +import { TryGetSendAccessTokenError } from "../types/try-get-send-access-token-error.type"; + +/** + * Service to manage send access tokens. + */ +export abstract class SendTokenService { + /** + * Attempts to retrieve a {@link SendAccessToken} for the given sendId. + * If the access token is found in session storage and is not expired, then it returns the token. + * If the access token is expired, then it returns a {@link TryGetSendAccessTokenError} expired error. + * If an access token is not found in storage, then it attempts to retrieve it from the server (will succeed for sends that don't require any credentials to view). + * If the access token is successfully retrieved from the server, then it stores the token in session storage and returns it. + * If an access token cannot be granted b/c the send requires credentials, then it returns a {@link TryGetSendAccessTokenError} indicating which credentials are required. + * Any submissions of credentials will be handled by the getSendAccessToken$ method. + * @param sendId The ID of the send to retrieve the access token for. + * @returns An observable that emits a SendAccessToken if successful, or a TryGetSendAccessTokenError if not. + */ + abstract tryGetSendAccessToken$: ( + sendId: string, + ) => Observable<SendAccessToken | TryGetSendAccessTokenError>; + + /** + * Retrieves a SendAccessToken for the given sendId using the provided credentials. + * If the access token is successfully retrieved from the server, it stores the token in session storage and returns it. + * If the access token cannot be granted due to invalid credentials, it returns a {@link GetSendAccessTokenError}. + * @param sendId The ID of the send to retrieve the access token for. + * @param sendAccessCredentials The credentials to use for accessing the send. + * @returns An observable that emits a SendAccessToken if successful, or a GetSendAccessTokenError if not. + */ + abstract getSendAccessToken$: ( + sendId: string, + sendAccessCredentials: SendAccessDomainCredentials, + ) => Observable<SendAccessToken | GetSendAccessTokenError>; + + /** + * Hashes a password for send access which is required to create a {@link SendAccessTokenRequest} + * (more specifically, to create a {@link SendAccessDomainCredentials} for sends that require a password) + * @param password The raw password string to hash. + * @param keyMaterialUrlB64 The base64 URL encoded key material string. + * @returns A promise that resolves to the hashed password as a SendHashedPasswordB64. + */ + abstract hashSendPassword: ( + password: string, + keyMaterialUrlB64: string, + ) => Promise<SendHashedPasswordB64>; + + /** + * Clears a send access token from storage. + */ + abstract invalidateSendAccessToken: (sendId: string) => Promise<void>; +} diff --git a/libs/common/src/auth/send-access/index.ts b/libs/common/src/auth/send-access/index.ts new file mode 100644 index 00000000000..38352451e49 --- /dev/null +++ b/libs/common/src/auth/send-access/index.ts @@ -0,0 +1,4 @@ +export * from "./abstractions"; +export * from "./models"; +export * from "./services"; +export * from "./types"; diff --git a/libs/common/src/auth/send-access/models/index.ts b/libs/common/src/auth/send-access/models/index.ts new file mode 100644 index 00000000000..9748d794715 --- /dev/null +++ b/libs/common/src/auth/send-access/models/index.ts @@ -0,0 +1 @@ +export * from "./send-access-token"; diff --git a/libs/common/src/auth/send-access/models/send-access-token.spec.ts b/libs/common/src/auth/send-access/models/send-access-token.spec.ts new file mode 100644 index 00000000000..6bacac7eb6d --- /dev/null +++ b/libs/common/src/auth/send-access/models/send-access-token.spec.ts @@ -0,0 +1,75 @@ +import { SendAccessTokenResponse } from "@bitwarden/sdk-internal"; + +import { SendAccessToken } from "./send-access-token"; + +describe("SendAccessToken", () => { + const sendId = "sendId"; + + const NOW = 1_000_000; // fixed timestamp for predictable results + + const expiresAt: number = NOW + 1000 * 60 * 5; // 5 minutes from now + + const expiredExpiresAt: number = NOW - 1000 * 60 * 5; // 5 minutes ago + + let nowSpy: jest.SpyInstance<number, []>; + + beforeAll(() => { + nowSpy = jest.spyOn(Date, "now"); + }); + + beforeEach(() => { + // Ensure every test starts from the same fixed time + nowSpy.mockReturnValue(NOW); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it("should create a valid, unexpired token", () => { + const token = new SendAccessToken(sendId, expiresAt); + expect(token).toBeTruthy(); + expect(token.isExpired()).toBe(false); + }); + + it("should be expired after the expiration time", () => { + const token = new SendAccessToken(sendId, expiredExpiresAt); + expect(token.isExpired()).toBe(true); + }); + + it("should be considered expired if within 5 seconds of expiration", () => { + const token = new SendAccessToken(sendId, expiresAt); + nowSpy.mockReturnValue(expiresAt - 4_000); // 4 seconds before expiry + expect(token.isExpired()).toBe(true); + }); + + it("should return the correct time until expiry in seconds", () => { + const token = new SendAccessToken(sendId, expiresAt); + expect(token.timeUntilExpirySeconds()).toBe(300); // 5 minutes + }); + + it("should return 0 if the token is expired", () => { + const token = new SendAccessToken(sendId, expiredExpiresAt); + expect(token.timeUntilExpirySeconds()).toBe(0); + }); + + it("should create a token from JSON", () => { + const json = { + token: sendId, + expiresAt: expiresAt, + }; + const token = SendAccessToken.fromJson(json); + expect(token).toBeTruthy(); + expect(token.isExpired()).toBe(false); + }); + + it("should create a token from SendAccessTokenResponse", () => { + const response = { + token: sendId, + expiresAt: expiresAt, + } as SendAccessTokenResponse; + const token = SendAccessToken.fromSendAccessTokenResponse(response); + expect(token).toBeTruthy(); + expect(token.isExpired()).toBe(false); + }); +}); diff --git a/libs/common/src/auth/send-access/models/send-access-token.ts b/libs/common/src/auth/send-access/models/send-access-token.ts new file mode 100644 index 00000000000..e237243159d --- /dev/null +++ b/libs/common/src/auth/send-access/models/send-access-token.ts @@ -0,0 +1,46 @@ +import { Jsonify } from "type-fest"; + +import { SendAccessTokenResponse } from "@bitwarden/sdk-internal"; + +export class SendAccessToken { + constructor( + /** + * The access token string + */ + readonly token: string, + /** + * The time (in milliseconds since the epoch) when the token expires + */ + readonly expiresAt: number, + ) {} + + /** Returns whether the send access token is expired or not + * Has a 5 second threshold to avoid race conditions with the token + * expiring in flight + */ + isExpired(threshold: number = 5_000): boolean { + return Date.now() >= this.expiresAt - threshold; + } + + /** Returns how many full seconds remain until expiry. Returns 0 if expired. */ + timeUntilExpirySeconds(): number { + return Math.max(0, Math.floor((this.expiresAt - Date.now()) / 1_000)); + } + + static fromJson(parsedJson: Jsonify<SendAccessToken>): SendAccessToken { + return new SendAccessToken(parsedJson.token, parsedJson.expiresAt); + } + + /** + * Creates a SendAccessToken from a SendAccessTokenResponse. + * @param sendAccessTokenResponse The SDK response object containing the token and expiry information. + * @returns A new instance of SendAccessToken. + * note: we need to convert from the SDK response type to our internal type so that we can + * be sure it will serialize/deserialize correctly in state provider. + */ + static fromSendAccessTokenResponse( + sendAccessTokenResponse: SendAccessTokenResponse, + ): SendAccessToken { + return new SendAccessToken(sendAccessTokenResponse.token, sendAccessTokenResponse.expiresAt); + } +} diff --git a/libs/common/src/auth/send-access/services/default-send-token.service.spec.ts b/libs/common/src/auth/send-access/services/default-send-token.service.spec.ts new file mode 100644 index 00000000000..8db0532911f --- /dev/null +++ b/libs/common/src/auth/send-access/services/default-send-token.service.spec.ts @@ -0,0 +1,678 @@ +import { MockProxy, mock } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; + +import { + SendAccessTokenApiErrorResponse, + SendAccessTokenError, + SendAccessTokenInvalidGrantError, + SendAccessTokenInvalidRequestError, + SendAccessTokenResponse, + UnexpectedIdentityError, +} from "@bitwarden/sdk-internal"; +import { FakeGlobalState, FakeGlobalStateProvider } from "@bitwarden/state-test-utils"; + +import { + SendHashedPassword, + SendPasswordKeyMaterial, + SendPasswordService, +} from "../../../key-management/sends"; +import { Utils } from "../../../platform/misc/utils"; +import { MockSdkService } from "../../../platform/spec/mock-sdk.service"; +import { SendAccessToken } from "../models/send-access-token"; +import { GetSendAccessTokenError } from "../types/get-send-access-token-error.type"; +import { SendAccessDomainCredentials } from "../types/send-access-domain-credentials.type"; +import { SendHashedPasswordB64 } from "../types/send-hashed-password-b64.type"; +import { SendOtp } from "../types/send-otp.type"; + +import { DefaultSendTokenService } from "./default-send-token.service"; +import { SEND_ACCESS_TOKEN_DICT } from "./send-access-token-dict.state"; + +describe("SendTokenService", () => { + let service: DefaultSendTokenService; + + // Deps + let sdkService: MockSdkService; + let globalStateProvider: FakeGlobalStateProvider; + let sendPasswordService: MockProxy<SendPasswordService>; + + beforeEach(() => { + globalStateProvider = new FakeGlobalStateProvider(); + sdkService = new MockSdkService(); + sendPasswordService = mock<SendPasswordService>(); + + service = new DefaultSendTokenService(globalStateProvider, sdkService, sendPasswordService); + }); + + it("instantiates", () => { + expect(service).toBeTruthy(); + }); + + describe("Send access token retrieval tests", () => { + let sendAccessTokenDictGlobalState: FakeGlobalState<Record<string, SendAccessToken>>; + + let sendAccessTokenResponse: SendAccessTokenResponse; + + let sendId: string; + let sendAccessToken: SendAccessToken; + let token: string; + let tokenExpiresAt: number; + + const EXPECTED_SERVER_KIND: GetSendAccessTokenError["kind"] = "expected_server"; + const UNEXPECTED_SERVER_KIND: GetSendAccessTokenError["kind"] = "unexpected_server"; + + const INVALID_REQUEST_CODES: SendAccessTokenInvalidRequestError[] = [ + "send_id_required", + "password_hash_b64_required", + "email_required", + "email_and_otp_required_otp_sent", + "unknown", + ]; + + const INVALID_GRANT_CODES: SendAccessTokenInvalidGrantError[] = [ + "send_id_invalid", + "password_hash_b64_invalid", + "email_invalid", + "otp_invalid", + "otp_generation_failed", + "unknown", + ]; + + const CREDS = [ + { kind: "password", passwordHashB64: "h4sh" as SendHashedPasswordB64 }, + { kind: "email", email: "u@example.com" }, + { kind: "email_otp", email: "u@example.com", otp: "123456" as SendOtp }, + ] as const satisfies readonly SendAccessDomainCredentials[]; + + type SendAccessTokenApiErrorResponseErrorCode = SendAccessTokenApiErrorResponse["error"]; + + type SimpleErrorType = Exclude< + SendAccessTokenApiErrorResponseErrorCode, + "invalid_request" | "invalid_grant" + >; + + // Extract out simple error types which don't have complex send_access_error_types to handle. + const SIMPLE_ERROR_TYPES = [ + "invalid_client", + "unauthorized_client", + "unsupported_grant_type", + "invalid_scope", + "invalid_target", + ] as const satisfies readonly SimpleErrorType[]; + + beforeEach(() => { + sendId = "sendId"; + token = "sendAccessToken"; + tokenExpiresAt = Date.now() + 1000 * 60 * 5; // 5 minutes from now + + sendAccessTokenResponse = { + token: token, + expiresAt: tokenExpiresAt, + }; + + sendAccessToken = SendAccessToken.fromSendAccessTokenResponse(sendAccessTokenResponse); + + sendAccessTokenDictGlobalState = globalStateProvider.getFake(SEND_ACCESS_TOKEN_DICT); + // Ensure the state is empty before each test + sendAccessTokenDictGlobalState.stateSubject.next({}); + }); + + describe("tryGetSendAccessToken$", () => { + it("returns the send access token from session storage when token exists and isn't expired", async () => { + // Arrange + // Store the send access token in the global state + sendAccessTokenDictGlobalState.stateSubject.next({ [sendId]: sendAccessToken }); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toEqual(sendAccessToken); + }); + + it("returns expired error and clears token from storage when token is expired", async () => { + // Arrange + const oldDate = new Date("2025-01-01"); + const expiredSendAccessToken = new SendAccessToken(token, oldDate.getTime()); + sendAccessTokenDictGlobalState.stateSubject.next({ [sendId]: expiredSendAccessToken }); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).not.toBeInstanceOf(SendAccessToken); + expect(result).toStrictEqual({ kind: "expired" }); + + // assert that we removed the expired token from storage. + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + expect(sendAccessTokenDict).not.toHaveProperty(sendId); + }); + + it("calls to get a new token if none is found in storage and stores the retrieved token in session storage", async () => { + // Arrange + sdkService.client.auth + .mockDeep() + .send_access.mockDeep() + .request_send_access_token.mockResolvedValue(sendAccessTokenResponse); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toBeInstanceOf(SendAccessToken); + expect(result).toEqual(sendAccessToken); + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + expect(sendAccessTokenDict).toHaveProperty(sendId, sendAccessToken); + }); + + describe("handles expected invalid_request scenarios appropriately", () => { + it.each(INVALID_REQUEST_CODES)( + "surfaces %s as an expected invalid_request error", + async (code) => { + // Arrange + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_request", + error_description: code, + send_access_error_type: code, + }; + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toEqual({ + kind: EXPECTED_SERVER_KIND, + error: sendAccessTokenApiErrorResponse, + }); + }, + ); + + it("handles bare expected invalid_request scenario appropriately", async () => { + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_request", + }; + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toEqual({ + kind: EXPECTED_SERVER_KIND, + error: sendAccessTokenApiErrorResponse, + }); + }); + }); + + it.each(SIMPLE_ERROR_TYPES)("handles expected %s error appropriately", async (errorType) => { + const api: SendAccessTokenApiErrorResponse = { + error: errorType, + error_description: `The ${errorType} error occurred`, + }; + mockSdkRejectWith({ kind: "expected", data: api }); + + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + expect(result).toEqual({ kind: EXPECTED_SERVER_KIND, error: api }); + }); + + it.each(SIMPLE_ERROR_TYPES)( + "handles expected %s bare error appropriately", + async (errorType) => { + const api: SendAccessTokenApiErrorResponse = { error: errorType }; + mockSdkRejectWith({ kind: "expected", data: api }); + + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + expect(result).toEqual({ kind: EXPECTED_SERVER_KIND, error: api }); + }, + ); + + describe("handles expected invalid_grant scenarios appropriately", () => { + it.each(INVALID_GRANT_CODES)( + "surfaces %s as an expected invalid_grant error", + async (code) => { + // Arrange + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_grant", + error_description: code, + send_access_error_type: code, + }; + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toEqual({ + kind: EXPECTED_SERVER_KIND, + error: sendAccessTokenApiErrorResponse, + }); + }, + ); + + it("handles bare expected invalid_grant scenario appropriately", async () => { + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_grant", + }; + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toEqual({ + kind: EXPECTED_SERVER_KIND, + error: sendAccessTokenApiErrorResponse, + }); + }); + }); + + it("surfaces unexpected errors as unexpected_server error", async () => { + // Arrange + const unexpectedIdentityError: UnexpectedIdentityError = "unexpected error occurred"; + + mockSdkRejectWith({ + kind: "unexpected", + data: unexpectedIdentityError, + }); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toEqual({ + kind: UNEXPECTED_SERVER_KIND, + error: unexpectedIdentityError, + }); + }); + + it("surfaces an unknown error as an unknown error", async () => { + // Arrange + const unknownError = "unknown error occurred"; + + sdkService.client.auth + .mockDeep() + .send_access.mockDeep() + .request_send_access_token.mockRejectedValue(new Error(unknownError)); + + // Act + const result = await firstValueFrom(service.tryGetSendAccessToken$(sendId)); + + // Assert + expect(result).toEqual({ + kind: "unknown", + error: unknownError, + }); + }); + + describe("getSendAccessTokenFromStorage", () => { + it("returns undefined if no token is found in storage", async () => { + const result = await (service as any).getSendAccessTokenFromStorage("nonexistentSendId"); + expect(result).toBeUndefined(); + }); + + it("returns the token if found in storage", async () => { + sendAccessTokenDictGlobalState.stateSubject.next({ [sendId]: sendAccessToken }); + const result = await (service as any).getSendAccessTokenFromStorage(sendId); + expect(result).toEqual(sendAccessToken); + }); + + it("returns undefined if the global state isn't initialized yet", async () => { + sendAccessTokenDictGlobalState.stateSubject.next(null); + + const result = await (service as any).getSendAccessTokenFromStorage(sendId); + expect(result).toBeUndefined(); + }); + }); + + describe("setSendAccessTokenInStorage", () => { + it("stores the token in storage", async () => { + await (service as any).setSendAccessTokenInStorage(sendId, sendAccessToken); + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + expect(sendAccessTokenDict).toHaveProperty(sendId, sendAccessToken); + }); + + it("initializes the dictionary if it isn't already", async () => { + sendAccessTokenDictGlobalState.stateSubject.next(null); + + await (service as any).setSendAccessTokenInStorage(sendId, sendAccessToken); + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + expect(sendAccessTokenDict).toHaveProperty(sendId, sendAccessToken); + }); + + it("merges with existing tokens in storage", async () => { + const anotherSendId = "anotherSendId"; + const anotherSendAccessToken = new SendAccessToken( + "anotherToken", + Date.now() + 1000 * 60, + ); + + sendAccessTokenDictGlobalState.stateSubject.next({ + [anotherSendId]: anotherSendAccessToken, + }); + await (service as any).setSendAccessTokenInStorage(sendId, sendAccessToken); + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + expect(sendAccessTokenDict).toHaveProperty(sendId, sendAccessToken); + expect(sendAccessTokenDict).toHaveProperty(anotherSendId, anotherSendAccessToken); + }); + }); + }); + + describe("getSendAccessToken$", () => { + it("returns a send access token for a password protected send when given valid password credentials", async () => { + // Arrange + const sendPasswordCredentials: SendAccessDomainCredentials = { + kind: "password", + passwordHashB64: "testPassword" as SendHashedPasswordB64, + }; + + sdkService.client.auth + .mockDeep() + .send_access.mockDeep() + .request_send_access_token.mockResolvedValue(sendAccessTokenResponse); + + // Act + const result = await firstValueFrom( + service.getSendAccessToken$(sendId, sendPasswordCredentials), + ); + + // Assert + expect(result).toEqual(sendAccessToken); + + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + expect(sendAccessTokenDict).toHaveProperty(sendId, sendAccessToken); + }); + + // Note: we deliberately aren't testing the "success" scenario of passing + // just SendEmailCredentials as that will never return a send access token on it's own. + + it("returns a send access token for a email + otp protected send when given valid email and otp", async () => { + // Arrange + const sendEmailOtpCredentials: SendAccessDomainCredentials = { + kind: "email_otp", + email: "test@example.com", + otp: "123456" as SendOtp, + }; + + sdkService.client.auth + .mockDeep() + .send_access.mockDeep() + .request_send_access_token.mockResolvedValue(sendAccessTokenResponse); + + // Act + const result = await firstValueFrom( + service.getSendAccessToken$(sendId, sendEmailOtpCredentials), + ); + + // Assert + expect(result).toEqual(sendAccessToken); + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + expect(sendAccessTokenDict).toHaveProperty(sendId, sendAccessToken); + }); + + describe.each(CREDS.map((c) => [c.kind, c] as const))( + "scenarios with %s credentials", + (_label, creds) => { + it.each(INVALID_REQUEST_CODES)( + "handles expected invalid_request.%s scenario appropriately", + async (code) => { + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_request", + error_description: code, + send_access_error_type: code, + }; + + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + const result = await firstValueFrom(service.getSendAccessToken$("abc123", creds)); + + expect(result).toEqual({ + kind: "expected_server", + error: sendAccessTokenApiErrorResponse, + }); + }, + ); + + it("handles expected invalid_request scenario appropriately", async () => { + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_request", + }; + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + // Act + const result = await firstValueFrom(service.getSendAccessToken$(sendId, creds)); + + // Assert + expect(result).toEqual({ + kind: "expected_server", + error: sendAccessTokenApiErrorResponse, + }); + }); + + it.each(INVALID_GRANT_CODES)( + "handles expected invalid_grant.%s scenario appropriately", + async (code) => { + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_grant", + error_description: code, + send_access_error_type: code, + }; + + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + const result = await firstValueFrom(service.getSendAccessToken$("abc123", creds)); + + expect(result).toEqual({ + kind: "expected_server", + error: sendAccessTokenApiErrorResponse, + }); + }, + ); + + it("handles expected invalid_grant scenario appropriately", async () => { + const sendAccessTokenApiErrorResponse: SendAccessTokenApiErrorResponse = { + error: "invalid_grant", + }; + mockSdkRejectWith({ + kind: "expected", + data: sendAccessTokenApiErrorResponse, + }); + + // Act + const result = await firstValueFrom(service.getSendAccessToken$(sendId, creds)); + + // Assert + expect(result).toEqual({ + kind: "expected_server", + error: sendAccessTokenApiErrorResponse, + }); + }); + + it.each(SIMPLE_ERROR_TYPES)( + "handles expected %s error appropriately", + async (errorType) => { + const api: SendAccessTokenApiErrorResponse = { + error: errorType, + error_description: `The ${errorType} error occurred`, + }; + mockSdkRejectWith({ kind: "expected", data: api }); + + const result = await firstValueFrom(service.getSendAccessToken$(sendId, creds)); + + expect(result).toEqual({ kind: EXPECTED_SERVER_KIND, error: api }); + }, + ); + + it.each(SIMPLE_ERROR_TYPES)( + "handles expected %s bare error appropriately", + async (errorType) => { + const api: SendAccessTokenApiErrorResponse = { error: errorType }; + mockSdkRejectWith({ kind: "expected", data: api }); + + const result = await firstValueFrom(service.getSendAccessToken$(sendId, creds)); + + expect(result).toEqual({ kind: EXPECTED_SERVER_KIND, error: api }); + }, + ); + + it("surfaces unexpected errors as unexpected_server error", async () => { + // Arrange + const unexpectedIdentityError: UnexpectedIdentityError = "unexpected error occurred"; + + mockSdkRejectWith({ + kind: "unexpected", + data: unexpectedIdentityError, + }); + + // Act + const result = await firstValueFrom(service.getSendAccessToken$(sendId, creds)); + + // Assert + expect(result).toEqual({ + kind: UNEXPECTED_SERVER_KIND, + error: unexpectedIdentityError, + }); + }); + + it("surfaces an unknown error as an unknown error", async () => { + // Arrange + const unknownError = "unknown error occurred"; + + sdkService.client.auth + .mockDeep() + .send_access.mockDeep() + .request_send_access_token.mockRejectedValue(new Error(unknownError)); + + // Act + const result = await firstValueFrom(service.getSendAccessToken$(sendId, creds)); + + // Assert + expect(result).toEqual({ + kind: "unknown", + error: unknownError, + }); + }); + }, + ); + + it("errors if passwordHashB64 is missing for password credentials", async () => { + const creds: SendAccessDomainCredentials = { + kind: "password", + passwordHashB64: "" as SendHashedPasswordB64, + }; + await expect(firstValueFrom(service.getSendAccessToken$(sendId, creds))).rejects.toThrow( + "passwordHashB64 must be provided for password credentials.", + ); + }); + + it("errors if email is missing for email credentials", async () => { + const creds: SendAccessDomainCredentials = { + kind: "email", + email: "", + }; + await expect(firstValueFrom(service.getSendAccessToken$(sendId, creds))).rejects.toThrow( + "email must be provided for email credentials.", + ); + }); + + it("errors if email or otp is missing for email_otp credentials", async () => { + const creds: SendAccessDomainCredentials = { + kind: "email_otp", + email: "", + otp: "" as SendOtp, + }; + await expect(firstValueFrom(service.getSendAccessToken$(sendId, creds))).rejects.toThrow( + "email and otp must be provided for email_otp credentials.", + ); + }); + }); + + describe("invalidateSendAccessToken", () => { + it("removes a send access token from storage", async () => { + // Arrange + sendAccessTokenDictGlobalState.stateSubject.next({ [sendId]: sendAccessToken }); + + // Act + await service.invalidateSendAccessToken(sendId); + const sendAccessTokenDict = await firstValueFrom(sendAccessTokenDictGlobalState.state$); + + // Assert + expect(sendAccessTokenDict).not.toHaveProperty(sendId); + }); + }); + }); + + describe("hashSendPassword", () => { + test.each(["", null, undefined])("rejects if password is %p", async (pwd) => { + await expect(service.hashSendPassword(pwd as any, "keyMaterialUrlB64")).rejects.toThrow( + "Password must be provided.", + ); + }); + + test.each(["", null, undefined])( + "rejects if keyMaterialUrlB64 is %p", + async (keyMaterialUrlB64) => { + await expect( + service.hashSendPassword("password", keyMaterialUrlB64 as any), + ).rejects.toThrow("KeyMaterialUrlB64 must be provided."); + }, + ); + + it("correctly hashes the password", async () => { + // Arrange + const password = "testPassword"; + const keyMaterialUrlB64 = "testKeyMaterialUrlB64"; + const keyMaterialArray = new Uint8Array([1, 2, 3]) as SendPasswordKeyMaterial; + const hashedPasswordArray = new Uint8Array([4, 5, 6]) as SendHashedPassword; + const sendHashedPasswordB64 = "hashedPasswordB64" as SendHashedPasswordB64; + + const utilsFromUrlB64ToArraySpy = jest + .spyOn(Utils, "fromUrlB64ToArray") + .mockReturnValue(keyMaterialArray); + + sendPasswordService.hashPassword.mockResolvedValue(hashedPasswordArray); + + const utilsFromBufferToB64Spy = jest + .spyOn(Utils, "fromBufferToB64") + .mockReturnValue(sendHashedPasswordB64); + + // Act + const result = await service.hashSendPassword(password, keyMaterialUrlB64); + + // Assert + expect(sendPasswordService.hashPassword).toHaveBeenCalledWith(password, keyMaterialArray); + expect(utilsFromUrlB64ToArraySpy).toHaveBeenCalledWith(keyMaterialUrlB64); + expect(utilsFromBufferToB64Spy).toHaveBeenCalledWith(hashedPasswordArray); + expect(result).toBe(sendHashedPasswordB64); + }); + }); + + function mockSdkRejectWith(sendAccessTokenError: SendAccessTokenError) { + sdkService.client.auth + .mockDeep() + .send_access.mockDeep() + .request_send_access_token.mockRejectedValue(sendAccessTokenError); + } +}); diff --git a/libs/common/src/auth/send-access/services/default-send-token.service.ts b/libs/common/src/auth/send-access/services/default-send-token.service.ts new file mode 100644 index 00000000000..4d3376fd5b6 --- /dev/null +++ b/libs/common/src/auth/send-access/services/default-send-token.service.ts @@ -0,0 +1,316 @@ +import { Observable, defer, firstValueFrom, from } from "rxjs"; + +import { + BitwardenClient, + SendAccessCredentials, + SendAccessTokenError, + SendAccessTokenRequest, + SendAccessTokenResponse, +} from "@bitwarden/sdk-internal"; +import { GlobalState, GlobalStateProvider } from "@bitwarden/state"; + +import { SendPasswordService } from "../../../key-management/sends/abstractions/send-password.service"; +import { + SendHashedPassword, + SendPasswordKeyMaterial, +} from "../../../key-management/sends/types/send-hashed-password.type"; +import { SdkService } from "../../../platform/abstractions/sdk/sdk.service"; +import { Utils } from "../../../platform/misc/utils"; +import { SendTokenService as SendTokenServiceAbstraction } from "../abstractions/send-token.service"; +import { SendAccessToken } from "../models/send-access-token"; +import { GetSendAccessTokenError } from "../types/get-send-access-token-error.type"; +import { SendAccessDomainCredentials } from "../types/send-access-domain-credentials.type"; +import { SendHashedPasswordB64 } from "../types/send-hashed-password-b64.type"; +import { TryGetSendAccessTokenError } from "../types/try-get-send-access-token-error.type"; + +import { SEND_ACCESS_TOKEN_DICT } from "./send-access-token-dict.state"; + +export class DefaultSendTokenService implements SendTokenServiceAbstraction { + private sendAccessTokenDictGlobalState: GlobalState<Record<string, SendAccessToken>> | undefined; + + constructor( + private globalStateProvider: GlobalStateProvider, + private sdkService: SdkService, + private sendPasswordService: SendPasswordService, + ) { + this.initializeState(); + } + + private initializeState(): void { + this.sendAccessTokenDictGlobalState = this.globalStateProvider.get(SEND_ACCESS_TOKEN_DICT); + } + + tryGetSendAccessToken$(sendId: string): Observable<SendAccessToken | TryGetSendAccessTokenError> { + // Defer the execution to ensure that a cold observable is returned. + return defer(() => from(this._tryGetSendAccessToken(sendId))); + } + + private async _tryGetSendAccessToken( + sendId: string, + ): Promise<SendAccessToken | TryGetSendAccessTokenError> { + // Validate the sendId is a non-empty string. + this.validateSendId(sendId); + + // Check in storage for the access token for the given sendId. + const sendAccessTokenFromStorage = await this.getSendAccessTokenFromStorage(sendId); + + if (sendAccessTokenFromStorage != null) { + // If it is expired, we clear the token from storage and return the expired error + if (sendAccessTokenFromStorage.isExpired()) { + await this.clearSendAccessTokenFromStorage(sendId); + return { kind: "expired" }; + } else { + // If it is not expired, we return it + return sendAccessTokenFromStorage; + } + } + + // If we don't have a token in storage, we can try to request a new token from the server. + const request: SendAccessTokenRequest = { + sendId: sendId, + }; + + const anonSdkClient: BitwardenClient = await firstValueFrom(this.sdkService.client$); + + try { + const result: SendAccessTokenResponse = await anonSdkClient + .auth() + .send_access() + .request_send_access_token(request); + + // Convert from SDK shape to SendAccessToken so it can be serialized into & out of state provider + const sendAccessToken = SendAccessToken.fromSendAccessTokenResponse(result); + + // If we get a token back, we need to store it in the global state. + await this.setSendAccessTokenInStorage(sendId, sendAccessToken); + + return sendAccessToken; + } catch (error: unknown) { + return this.normalizeSendAccessTokenError(error); + } + } + + getSendAccessToken$( + sendId: string, + sendCredentials: SendAccessDomainCredentials, + ): Observable<SendAccessToken | GetSendAccessTokenError> { + // Defer the execution to ensure that a cold observable is returned. + return defer(() => from(this._getSendAccessToken(sendId, sendCredentials))); + } + + private async _getSendAccessToken( + sendId: string, + sendAccessCredentials: SendAccessDomainCredentials, + ): Promise<SendAccessToken | GetSendAccessTokenError> { + // Validate inputs to account for non-strict TS call sites. + this.validateCredentialsRequest(sendId, sendAccessCredentials); + + // Convert inputs to SDK request shape + const request: SendAccessTokenRequest = { + sendId: sendId, + sendAccessCredentials: this.convertDomainCredentialsToSdkCredentials(sendAccessCredentials), + }; + + const anonSdkClient: BitwardenClient = await firstValueFrom(this.sdkService.client$); + + try { + const result: SendAccessTokenResponse = await anonSdkClient + .auth() + .send_access() + .request_send_access_token(request); + + // Convert from SDK interface to SendAccessToken class so it can be serialized into & out of state provider + const sendAccessToken = SendAccessToken.fromSendAccessTokenResponse(result); + + // Any time we get a token from the server, we need to store it in the global state. + await this.setSendAccessTokenInStorage(sendId, sendAccessToken); + + return sendAccessToken; + } catch (error: unknown) { + return this.normalizeSendAccessTokenError(error); + } + } + + async invalidateSendAccessToken(sendId: string): Promise<void> { + await this.clearSendAccessTokenFromStorage(sendId); + } + + async hashSendPassword( + password: string, + keyMaterialUrlB64: string, + ): Promise<SendHashedPasswordB64> { + // Validate the password and key material + if (password == null || password.trim() === "") { + throw new Error("Password must be provided."); + } + if (keyMaterialUrlB64 == null || keyMaterialUrlB64.trim() === "") { + throw new Error("KeyMaterialUrlB64 must be provided."); + } + + // Convert the base64 URL encoded key material to a Uint8Array + const keyMaterialUrlB64Array = Utils.fromUrlB64ToArray( + keyMaterialUrlB64, + ) as SendPasswordKeyMaterial; + + const sendHashedPasswordArray: SendHashedPassword = await this.sendPasswordService.hashPassword( + password, + keyMaterialUrlB64Array, + ); + + // Convert the Uint8Array to a base64 encoded string which is required + // for the server to be able to compare the password hash. + const sendHashedPasswordB64 = Utils.fromBufferToB64( + sendHashedPasswordArray, + ) as SendHashedPasswordB64; + + return sendHashedPasswordB64; + } + + private async getSendAccessTokenFromStorage( + sendId: string, + ): Promise<SendAccessToken | undefined> { + if (this.sendAccessTokenDictGlobalState != null) { + const sendAccessTokenDict = await firstValueFrom(this.sendAccessTokenDictGlobalState.state$); + return sendAccessTokenDict?.[sendId]; + } + return undefined; + } + + private async setSendAccessTokenInStorage( + sendId: string, + sendAccessToken: SendAccessToken, + ): Promise<void> { + if (this.sendAccessTokenDictGlobalState != null) { + await this.sendAccessTokenDictGlobalState.update( + (sendAccessTokenDict) => { + sendAccessTokenDict ??= {}; // Initialize if undefined + + sendAccessTokenDict[sendId] = sendAccessToken; + return sendAccessTokenDict; + }, + { + // only update if the value is different (to avoid unnecessary writes) + shouldUpdate: (prevDict) => { + const prevSendAccessToken = prevDict?.[sendId]; + return ( + prevSendAccessToken?.token !== sendAccessToken.token || + prevSendAccessToken?.expiresAt !== sendAccessToken.expiresAt + ); + }, + }, + ); + } + } + + private async clearSendAccessTokenFromStorage(sendId: string): Promise<void> { + if (this.sendAccessTokenDictGlobalState != null) { + await this.sendAccessTokenDictGlobalState.update( + (sendAccessTokenDict) => { + if (!sendAccessTokenDict) { + // If the dict is empty or undefined, there's nothing to clear + return sendAccessTokenDict; + } + + if (sendAccessTokenDict[sendId] == null) { + // If the specific sendId does not exist, nothing to clear + return sendAccessTokenDict; + } + + // Destructure to omit the specific sendId and get new reference for the rest of the dict for an immutable update + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { [sendId]: _, ...rest } = sendAccessTokenDict; + + return rest; + }, + { + // only update if the value is defined (to avoid unnecessary writes) + shouldUpdate: (prevDict) => prevDict?.[sendId] != null, + }, + ); + } + } + + /** + * Normalizes an error from the SDK send access token request process. + * @param e The error to normalize. + * @returns A normalized GetSendAccessTokenError. + */ + private normalizeSendAccessTokenError(e: unknown): GetSendAccessTokenError { + if (this.isSendAccessTokenError(e)) { + if (e.kind === "unexpected") { + return { kind: "unexpected_server", error: e.data }; + } + return { kind: "expected_server", error: e.data }; + } + + if (e instanceof Error) { + return { kind: "unknown", error: e.message }; + } + + try { + return { kind: "unknown", error: JSON.stringify(e) }; + } catch { + return { kind: "unknown", error: "error cannot be stringified" }; + } + } + + private isSendAccessTokenError(e: unknown): e is SendAccessTokenError { + return ( + typeof e === "object" && + e !== null && + "kind" in e && + (e.kind === "expected" || e.kind === "unexpected") + ); + } + + private validateSendId(sendId: string): void { + if (sendId == null || sendId.trim() === "") { + throw new Error("sendId must be provided."); + } + } + + private validateCredentialsRequest( + sendId: string, + sendAccessCredentials: SendAccessDomainCredentials, + ): void { + this.validateSendId(sendId); + if (sendAccessCredentials == null) { + throw new Error("sendAccessCredentials must be provided."); + } + + if (sendAccessCredentials.kind === "password" && !sendAccessCredentials.passwordHashB64) { + throw new Error("passwordHashB64 must be provided for password credentials."); + } + + if (sendAccessCredentials.kind === "email" && !sendAccessCredentials.email) { + throw new Error("email must be provided for email credentials."); + } + + if ( + sendAccessCredentials.kind === "email_otp" && + (!sendAccessCredentials.email || !sendAccessCredentials.otp) + ) { + throw new Error("email and otp must be provided for email_otp credentials."); + } + } + + private convertDomainCredentialsToSdkCredentials( + sendAccessCredentials: SendAccessDomainCredentials, + ): SendAccessCredentials { + switch (sendAccessCredentials.kind) { + case "password": + return { + passwordHashB64: sendAccessCredentials.passwordHashB64, + }; + case "email": + return { + email: sendAccessCredentials.email, + }; + case "email_otp": + return { + email: sendAccessCredentials.email, + otp: sendAccessCredentials.otp, + }; + } + } +} diff --git a/libs/common/src/auth/send-access/services/index.ts b/libs/common/src/auth/send-access/services/index.ts new file mode 100644 index 00000000000..f45ffb94bab --- /dev/null +++ b/libs/common/src/auth/send-access/services/index.ts @@ -0,0 +1 @@ +export * from "./default-send-token.service"; diff --git a/libs/common/src/auth/send-access/services/send-access-token-dict.state.ts b/libs/common/src/auth/send-access/services/send-access-token-dict.state.ts new file mode 100644 index 00000000000..77e390768fc --- /dev/null +++ b/libs/common/src/auth/send-access/services/send-access-token-dict.state.ts @@ -0,0 +1,15 @@ +import { Jsonify } from "type-fest"; + +import { KeyDefinition, SEND_ACCESS_DISK } from "@bitwarden/state"; + +import { SendAccessToken } from "../models/send-access-token"; + +export const SEND_ACCESS_TOKEN_DICT = KeyDefinition.record<SendAccessToken, string>( + SEND_ACCESS_DISK, + "accessTokenDict", + { + deserializer: (sendAccessTokenJson: Jsonify<SendAccessToken>) => { + return SendAccessToken.fromJson(sendAccessTokenJson); + }, + }, +); diff --git a/libs/common/src/auth/send-access/types/get-send-access-token-error.type.ts b/libs/common/src/auth/send-access/types/get-send-access-token-error.type.ts new file mode 100644 index 00000000000..224e982c84c --- /dev/null +++ b/libs/common/src/auth/send-access/types/get-send-access-token-error.type.ts @@ -0,0 +1,12 @@ +import { UnexpectedIdentityError, SendAccessTokenApiErrorResponse } from "@bitwarden/sdk-internal"; + +/** + * Represents the possible errors that can occur when retrieving a SendAccessToken. + * Note: for expected_server errors, see invalid-request-errors.type.ts and + * invalid-grant-errors.type.ts for type guards that identify specific + * SendAccessTokenApiErrorResponse errors + */ +export type GetSendAccessTokenError = + | { kind: "unexpected_server"; error: UnexpectedIdentityError } + | { kind: "expected_server"; error: SendAccessTokenApiErrorResponse } + | { kind: "unknown"; error: string }; diff --git a/libs/common/src/auth/send-access/types/index.ts b/libs/common/src/auth/send-access/types/index.ts new file mode 100644 index 00000000000..344ac00abbe --- /dev/null +++ b/libs/common/src/auth/send-access/types/index.ts @@ -0,0 +1,7 @@ +export * from "./try-get-send-access-token-error.type"; +export * from "./send-otp.type"; +export * from "./send-hashed-password-b64.type"; +export * from "./send-access-domain-credentials.type"; +export * from "./invalid-request-errors.type"; +export * from "./invalid-grant-errors.type"; +export * from "./get-send-access-token-error.type"; diff --git a/libs/common/src/auth/send-access/types/invalid-grant-errors.type.ts b/libs/common/src/auth/send-access/types/invalid-grant-errors.type.ts new file mode 100644 index 00000000000..befb869a89e --- /dev/null +++ b/libs/common/src/auth/send-access/types/invalid-grant-errors.type.ts @@ -0,0 +1,62 @@ +import { SendAccessTokenApiErrorResponse } from "@bitwarden/sdk-internal"; + +export type InvalidGrant = Extract<SendAccessTokenApiErrorResponse, { error: "invalid_grant" }>; + +export function isInvalidGrant(e: SendAccessTokenApiErrorResponse): e is InvalidGrant { + return e.error === "invalid_grant"; +} + +export type BareInvalidGrant = Extract< + SendAccessTokenApiErrorResponse, + { error: "invalid_grant" } +> & { send_access_error_type?: undefined }; + +export function isBareInvalidGrant(e: SendAccessTokenApiErrorResponse): e is BareInvalidGrant { + return e.error === "invalid_grant" && e.send_access_error_type === undefined; +} + +export type SendIdInvalid = InvalidGrant & { + send_access_error_type: "send_id_invalid"; +}; +export function sendIdInvalid(e: SendAccessTokenApiErrorResponse): e is SendIdInvalid { + return e.error === "invalid_grant" && e.send_access_error_type === "send_id_invalid"; +} + +export type PasswordHashB64Invalid = InvalidGrant & { + send_access_error_type: "password_hash_b64_invalid"; +}; +export function passwordHashB64Invalid( + e: SendAccessTokenApiErrorResponse, +): e is PasswordHashB64Invalid { + return e.error === "invalid_grant" && e.send_access_error_type === "password_hash_b64_invalid"; +} + +export type EmailInvalid = InvalidGrant & { + send_access_error_type: "email_invalid"; +}; +export function emailInvalid(e: SendAccessTokenApiErrorResponse): e is EmailInvalid { + return e.error === "invalid_grant" && e.send_access_error_type === "email_invalid"; +} + +export type OtpInvalid = InvalidGrant & { + send_access_error_type: "otp_invalid"; +}; +export function otpInvalid(e: SendAccessTokenApiErrorResponse): e is OtpInvalid { + return e.error === "invalid_grant" && e.send_access_error_type === "otp_invalid"; +} + +export type OtpGenerationFailed = InvalidGrant & { + send_access_error_type: "otp_generation_failed"; +}; +export function otpGenerationFailed(e: SendAccessTokenApiErrorResponse): e is OtpGenerationFailed { + return e.error === "invalid_grant" && e.send_access_error_type === "otp_generation_failed"; +} + +export type UnknownInvalidGrant = InvalidGrant & { + send_access_error_type: "unknown"; +}; +export function isUnknownInvalidGrant( + e: SendAccessTokenApiErrorResponse, +): e is UnknownInvalidGrant { + return e.error === "invalid_grant" && e.send_access_error_type === "unknown"; +} diff --git a/libs/common/src/auth/send-access/types/invalid-request-errors.type.ts b/libs/common/src/auth/send-access/types/invalid-request-errors.type.ts new file mode 100644 index 00000000000..57a70e62586 --- /dev/null +++ b/libs/common/src/auth/send-access/types/invalid-request-errors.type.ts @@ -0,0 +1,62 @@ +import { SendAccessTokenApiErrorResponse } from "@bitwarden/sdk-internal"; + +export type InvalidRequest = Extract<SendAccessTokenApiErrorResponse, { error: "invalid_request" }>; + +export function isInvalidRequest(e: SendAccessTokenApiErrorResponse): e is InvalidRequest { + return e.error === "invalid_request"; +} + +export type BareInvalidRequest = Extract< + SendAccessTokenApiErrorResponse, + { error: "invalid_request" } +> & { send_access_error_type?: undefined }; + +export function isBareInvalidRequest(e: SendAccessTokenApiErrorResponse): e is BareInvalidRequest { + return e.error === "invalid_request" && e.send_access_error_type === undefined; +} + +export type SendIdRequired = InvalidRequest & { + send_access_error_type: "send_id_required"; +}; + +export function sendIdRequired(e: SendAccessTokenApiErrorResponse): e is SendIdRequired { + return e.error === "invalid_request" && e.send_access_error_type === "send_id_required"; +} + +export type PasswordHashB64Required = InvalidRequest & { + send_access_error_type: "password_hash_b64_required"; +}; + +export function passwordHashB64Required( + e: SendAccessTokenApiErrorResponse, +): e is PasswordHashB64Required { + return e.error === "invalid_request" && e.send_access_error_type === "password_hash_b64_required"; +} + +export type EmailRequired = InvalidRequest & { send_access_error_type: "email_required" }; + +export function emailRequired(e: SendAccessTokenApiErrorResponse): e is EmailRequired { + return e.error === "invalid_request" && e.send_access_error_type === "email_required"; +} + +export type EmailAndOtpRequiredEmailSent = InvalidRequest & { + send_access_error_type: "email_and_otp_required_otp_sent"; +}; + +export function emailAndOtpRequiredEmailSent( + e: SendAccessTokenApiErrorResponse, +): e is EmailAndOtpRequiredEmailSent { + return ( + e.error === "invalid_request" && e.send_access_error_type === "email_and_otp_required_otp_sent" + ); +} + +export type UnknownInvalidRequest = InvalidRequest & { + send_access_error_type: "unknown"; +}; + +export function isUnknownInvalidRequest( + e: SendAccessTokenApiErrorResponse, +): e is UnknownInvalidRequest { + return e.error === "invalid_request" && e.send_access_error_type === "unknown"; +} diff --git a/libs/common/src/auth/send-access/types/send-access-domain-credentials.type.ts b/libs/common/src/auth/send-access/types/send-access-domain-credentials.type.ts new file mode 100644 index 00000000000..966117dc64e --- /dev/null +++ b/libs/common/src/auth/send-access/types/send-access-domain-credentials.type.ts @@ -0,0 +1,11 @@ +import { SendHashedPasswordB64 } from "./send-hashed-password-b64.type"; +import { SendOtp } from "./send-otp.type"; + +/** + * The domain facing send access credentials + * Will be internally mapped to the SDK types + */ +export type SendAccessDomainCredentials = + | { kind: "password"; passwordHashB64: SendHashedPasswordB64 } + | { kind: "email"; email: string } + | { kind: "email_otp"; email: string; otp: SendOtp }; diff --git a/libs/common/src/auth/send-access/types/send-hashed-password-b64.type.ts b/libs/common/src/auth/send-access/types/send-hashed-password-b64.type.ts new file mode 100644 index 00000000000..9d55bdfb671 --- /dev/null +++ b/libs/common/src/auth/send-access/types/send-hashed-password-b64.type.ts @@ -0,0 +1,3 @@ +import { Opaque } from "type-fest"; + +export type SendHashedPasswordB64 = Opaque<string, "SendHashedPasswordB64">; diff --git a/libs/common/src/auth/send-access/types/send-otp.type.ts b/libs/common/src/auth/send-access/types/send-otp.type.ts new file mode 100644 index 00000000000..b5dfaa95ac5 --- /dev/null +++ b/libs/common/src/auth/send-access/types/send-otp.type.ts @@ -0,0 +1,3 @@ +import { Opaque } from "type-fest"; + +export type SendOtp = Opaque<string, "SendOtp">; diff --git a/libs/common/src/auth/send-access/types/try-get-send-access-token-error.type.ts b/libs/common/src/auth/send-access/types/try-get-send-access-token-error.type.ts new file mode 100644 index 00000000000..e82b426654c --- /dev/null +++ b/libs/common/src/auth/send-access/types/try-get-send-access-token-error.type.ts @@ -0,0 +1,7 @@ +import { GetSendAccessTokenError } from "./get-send-access-token-error.type"; + +/** + * Represents the possible errors that can occur when trying to retrieve a SendAccessToken by + * just a sendId. Extends {@link GetSendAccessTokenError}. + */ +export type TryGetSendAccessTokenError = { kind: "expired" } | GetSendAccessTokenError; diff --git a/libs/state/src/core/state-definitions.ts b/libs/state/src/core/state-definitions.ts index 9968908a06f..e3ffa457e10 100644 --- a/libs/state/src/core/state-definitions.ts +++ b/libs/state/src/core/state-definitions.ts @@ -72,6 +72,7 @@ export const TOKEN_DISK_LOCAL = new StateDefinition("tokenDiskLocal", "disk", { web: "disk-local", }); export const TOKEN_MEMORY = new StateDefinition("token", "memory"); +export const SEND_ACCESS_DISK = new StateDefinition("sendAccess", "disk"); export const TWO_FACTOR_MEMORY = new StateDefinition("twoFactor", "memory"); export const USER_DECRYPTION_OPTIONS_DISK = new StateDefinition("userDecryptionOptions", "disk"); export const ORGANIZATION_INVITE_DISK = new StateDefinition("organizationInvite", "disk"); diff --git a/package-lock.json b/package-lock.json index 1b126255e63..52c156d25eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@angular/platform-browser": "19.2.14", "@angular/platform-browser-dynamic": "19.2.14", "@angular/router": "19.2.14", - "@bitwarden/sdk-internal": "0.2.0-main.296", + "@bitwarden/sdk-internal": "0.2.0-main.311", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4688,9 +4688,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.296", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.296.tgz", - "integrity": "sha512-SDTWRwnR+KritfgJVBgWKd27TJxl4IlUdTldVJ/tA0qM5OqGWrY6s4ubtl5eaGIl2X4WYRAvpe+VR93FLakk6A==", + "version": "0.2.0-main.311", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.311.tgz", + "integrity": "sha512-zJdQykNMFOyivpNaCB9jc85wZ1ci2HM8/E4hI+yS7FgRm0sRigK5rieF3+xRjiq7pEsZSD8AucR+u/XK9ADXiw==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index e94d0e98522..127e375b92c 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "@angular/platform-browser": "19.2.14", "@angular/platform-browser-dynamic": "19.2.14", "@angular/router": "19.2.14", - "@bitwarden/sdk-internal": "0.2.0-main.296", + "@bitwarden/sdk-internal": "0.2.0-main.311", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From 65d56ca2f3697d89ee4f3a58f2a73b506bc2fd0f Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Thu, 2 Oct 2025 06:20:21 -0700 Subject: [PATCH 34/83] [PM-25481] Update copy in Admin-Console export-page (#16594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support for export-scope-callout.component to conditionally render organizational export message • use config service to capture feature flag status • use platform service and routing to determine admin console context --- apps/browser/src/_locales/en/messages.json | 18 ++ apps/desktop/src/locales/en/messages.json | 18 ++ apps/web/src/locales/en/messages.json | 18 ++ .../export-scope-callout.component.ts | 68 +++--- .../src/components/export.component.html | 5 +- .../src/components/export.component.ts | 211 +++++++++++++++--- 6 files changed, 272 insertions(+), 66 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 4e399a530e1..771759d9f25 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3198,6 +3198,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index a9b5efda357..b2625bc85f0 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2654,6 +2654,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 50b361f3d5a..c6901a21824 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7038,6 +7038,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts index 2b03234c5e2..a85048c23fa 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts @@ -5,12 +5,10 @@ import { Component, effect, input } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { getById } from "@bitwarden/common/platform/misc/rxjs-operators"; import { CalloutModule } from "@bitwarden/components"; @Component({ @@ -30,6 +28,8 @@ export class ExportScopeCalloutComponent { readonly organizationId = input<string>(); /* Optional export format, determines which individual export description to display */ readonly exportFormat = input<string>(); + /* The description key to use for organizational exports */ + readonly orgExportDescription = input<string>(); constructor( protected organizationService: OrganizationService, @@ -37,35 +37,45 @@ export class ExportScopeCalloutComponent { ) { effect(async () => { this.show = false; - await this.getScopeMessage(this.organizationId(), this.exportFormat()); + await this.getScopeMessage( + this.organizationId(), + this.exportFormat(), + this.orgExportDescription(), + ); this.show = true; }); } - private async getScopeMessage(organizationId: string, exportFormat: string): Promise<void> { + private async getScopeMessage( + organizationId: string, + exportFormat: string, + orgExportDescription: string, + ): Promise<void> { const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.scopeConfig = - organizationId != null - ? { - title: "exportingOrganizationVaultTitle", - description: "exportingOrganizationVaultDesc", - scopeIdentifier: ( - await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(organizationId)), - ) - ).name, - } - : { - title: "exportingPersonalVaultTitle", - description: - exportFormat == "zip" - ? "exportingIndividualVaultWithAttachmentsDescription" - : "exportingIndividualVaultDescription", - scopeIdentifier: await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ), - }; + + if (organizationId != null) { + // exporting from organizational vault + const org = await firstValueFrom( + this.organizationService.organizations$(userId).pipe(getById(organizationId)), + ); + + this.scopeConfig = { + title: "exportingOrganizationVaultTitle", + description: orgExportDescription, + scopeIdentifier: org?.name ?? "", + }; + } else { + this.scopeConfig = { + // exporting from individual vault + title: "exportingPersonalVaultTitle", + description: + exportFormat === "zip" + ? "exportingIndividualVaultWithAttachmentsDescription" + : "exportingIndividualVaultDescription", + scopeIdentifier: + (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.email)))) ?? + "", + }; + } } } diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html index b33b01d3b13..c638e5d7dde 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.html @@ -8,6 +8,7 @@ <tools-export-scope-callout [organizationId]="organizationId" [exportFormat]="format" + [orgExportDescription]="orgExportDescription" ></tools-export-scope-callout> <form [formGroup]="exportForm" [bitSubmit]="submit" id="export_form_exportForm"> @@ -19,10 +20,10 @@ [label]="'myVault' | i18n" value="myVault" icon="bwi-user" - *ngIf="!(organizationDataOwnershipPolicy$ | async)" + *ngIf="!(organizationDataOwnershipPolicyAppliesToUser$ | async)" /> <bit-option - *ngFor="let o of organizations$ | async" + *ngFor="let o of organizations" [value]="o.id" [label]="o.name" icon="bwi-business" diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index f2caf4fe3f4..567480ac1bd 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -10,14 +10,20 @@ import { OnInit, Output, ViewChild, + Optional, } from "@angular/core"; import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/forms"; +import { Router } from "@angular/router"; import { + BehaviorSubject, combineLatest, firstValueFrom, + from, map, merge, Observable, + of, + shareReplay, startWith, Subject, switchMap, @@ -36,10 +42,13 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { EventType } from "@bitwarden/common/enums"; +import { ClientType, EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { getById } from "@bitwarden/common/platform/misc"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { pin } from "@bitwarden/common/tools/rx"; @@ -84,11 +93,9 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component"; ], }) export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { - private _organizationId: OrganizationId | undefined; - - get organizationId(): OrganizationId | undefined { - return this._organizationId; - } + private _organizationId$ = new BehaviorSubject<OrganizationId | undefined>(undefined); + private createDefaultLocationFlagEnabled$: Observable<boolean>; + private _showExcludeMyItems = false; /** * Enables the hosting control to pass in an organizationId @@ -96,29 +103,57 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { */ @Input() set organizationId(value: OrganizationId | string | undefined) { if (Utils.isNullOrEmpty(value)) { - this._organizationId = undefined; + this._organizationId$.next(undefined); return; } if (!isId<OrganizationId>(value)) { - this._organizationId = undefined; + this._organizationId$.next(undefined); return; } - this._organizationId = value; + this._organizationId$.next(value); getUserId(this.accountService.activeAccount$) .pipe( - switchMap((userId) => - this.organizationService.organizations$(userId).pipe(getById(this._organizationId)), - ), + switchMap((userId) => this.organizationService.organizations$(userId).pipe(getById(value))), ) .pipe(takeUntil(this.destroy$)) .subscribe((organization) => { - this._organizationId = organization?.id; + this._organizationId$.next(organization?.id); }); } + get organizationId(): OrganizationId | undefined { + return this._organizationId$.value; + } + + get showExcludeMyItems(): boolean { + return this._showExcludeMyItems; + } + + get orgExportDescription(): string { + if (!this._showExcludeMyItems) { + return "exportingOrganizationVaultDesc"; + } + return this.isAdminConsoleContext + ? "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc" + : "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc"; + } + + private get isAdminConsoleContext(): boolean { + const isWeb = this.platformUtilsService.getClientType?.() === ClientType.Web; + if (!isWeb || !this.router) { + return false; + } + try { + const url = this.router.url ?? ""; + return url.includes("/organizations/"); + } catch { + return false; + } + } + /** * The hosting control also needs a bitSubmitDirective (on the Submit button) which calls this components {@link submit}-method. * This components formState (loading/disabled) is emitted back up to the hosting component so for example the Submit button can be enabled/disabled and show loading state. @@ -143,7 +178,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { /** * Emits when the creation and download of the export-file have succeeded * - Emits an undefined when exporting from an individual vault - * - Emits the organizationId when exporting from an organizationl vault + * - Emits the organizationId when exporting from an organizational vault * */ @Output() onSuccessfulExport = new EventEmitter<OrganizationId | undefined>(); @@ -162,7 +197,10 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { } disablePersonalVaultExportPolicy$: Observable<boolean>; - organizationDataOwnershipPolicy$: Observable<boolean>; + // detects if policy is enabled and applies to the user, admins are exempted + organizationDataOwnershipPolicyAppliesToUser$: Observable<boolean>; + // detects if policy is enabled regardless of admin exemption + organizationDataOwnershipPolicyEnabledForOrg$: Observable<boolean>; exportForm = this.formBuilder.group({ vaultSelector: [ @@ -203,14 +241,46 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { protected organizationService: OrganizationService, private accountService: AccountService, private collectionService: CollectionService, + private configService: ConfigService, + private platformUtilsService: PlatformUtilsService, + @Optional() private router?: Router, ) {} async ngOnInit() { - // Setup subscription to emit when this form is enabled/disabled + this.observeFeatureFlags(); + this.observeFormState(); + this.observePolicyStatus(); + this.observeFormSelections(); + + // order is important below this line + this.observeMyItemsExclusionCriteria(); + this.observeValidatorAdjustments(); + this.setupPasswordGeneration(); + + if (this.organizationId) { + // organization vault export + this.initOrganizationOnly(); + return; + } + + // individual vault export + this.initIndividual(); + this.setupPolicyBasedFormState(); + } + + private observeFeatureFlags(): void { + this.createDefaultLocationFlagEnabled$ = from( + this.configService.getFeatureFlag(FeatureFlag.CreateDefaultLocation), + ).pipe(shareReplay({ bufferSize: 1, refCount: true })); + } + + private observeFormState(): void { this.exportForm.statusChanges.pipe(takeUntil(this.destroy$)).subscribe((c) => { this.formDisabled.emit(c === "DISABLED"); }); + } + private observePolicyStatus(): void { this.disablePersonalVaultExportPolicy$ = this.accountService.activeAccount$.pipe( getUserId, switchMap((userId) => @@ -218,13 +288,42 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { ), ); - this.organizationDataOwnershipPolicy$ = this.accountService.activeAccount$.pipe( + // when true, html template will hide "My Vault" option in vault selector drop down + this.organizationDataOwnershipPolicyAppliesToUser$ = this.accountService.activeAccount$.pipe( getUserId, switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId), ), ); + /* + Determines how organization exports are described in the callout. + Admins are exempted from organization data ownership policy, + and so this needs to determine if the policy is enabled for the org, not if it applies to the user. + */ + this.organizationDataOwnershipPolicyEnabledForOrg$ = combineLatest([ + this.accountService.activeAccount$.pipe(getUserId), + this._organizationId$, + ]).pipe( + switchMap(([userId, organizationId]) => { + if (!organizationId || !userId) { + return of(false); + } + return this.policyService.policies$(userId).pipe( + map((policies) => { + const policy = policies?.find( + (p) => + p.type === PolicyType.OrganizationDataOwnership && + p.organizationId === organizationId, + ); + return policy?.enabled ?? false; + }), + ); + }), + ); + } + + private observeFormSelections(): void { this.exportForm.controls.vaultSelector.valueChanges .pipe(takeUntil(this.destroy$)) .subscribe((value) => { @@ -236,15 +335,50 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { this.formatOptions.push({ name: ".zip (with attachments)", value: "zip" }); } }); + } + /** + * Determine value of showExcludeMyItems. Returns true when: + * CreateDefaultLocation feature flag is on + * AND organizationDataOwnershipPolicy is enabled for the selected organization + * AND a valid OrganizationId is present (not exporting from individual vault) + */ + private observeMyItemsExclusionCriteria(): void { + combineLatest({ + createDefaultLocationFlagEnabled: this.createDefaultLocationFlagEnabled$, + organizationDataOwnershipPolicyEnabledForOrg: + this.organizationDataOwnershipPolicyEnabledForOrg$, + organizationId: this._organizationId$, + }) + .pipe(takeUntil(this.destroy$)) + .subscribe( + ({ + createDefaultLocationFlagEnabled, + organizationDataOwnershipPolicyEnabledForOrg, + organizationId, + }) => { + if (!createDefaultLocationFlagEnabled || !organizationId) { + this._showExcludeMyItems = false; + return; + } + + this._showExcludeMyItems = organizationDataOwnershipPolicyEnabledForOrg; + }, + ); + } + + // Setup validator adjustments based on format and encryption type changes + private observeValidatorAdjustments(): void { merge( this.exportForm.get("format").valueChanges, this.exportForm.get("fileEncryptionType").valueChanges, ) .pipe(startWith(0), takeUntil(this.destroy$)) .subscribe(() => this.adjustValidators()); + } - // Wire up the password generation for the password-protected export + // Wire up the password generation for password-protected exports + private setupPasswordGeneration(): void { const account$ = this.accountService.activeAccount$.pipe( pin({ name() { @@ -255,6 +389,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { }, }), ); + this.generatorService .generate$({ on$: this.onGenerate$, account$ }) .pipe(takeUntil(this.destroy$)) @@ -264,23 +399,29 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { confirmFilePassword: generated.credential, }); }); + } - if (this.organizationId) { - this.organizations$ = this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => - this.organizationService - .memberOrganizations$(userId) - .pipe(map((orgs) => orgs.filter((org) => org.id == this.organizationId))), - ), - ); - this.exportForm.controls.vaultSelector.patchValue(this.organizationId); - this.exportForm.controls.vaultSelector.disable(); + /* + Initialize component for organization only export + Hides "My Vault" option by returning immediately + */ + private initOrganizationOnly(): void { + this.organizations$ = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.organizationService + .memberOrganizations$(userId) + .pipe(map((orgs) => orgs.filter((org) => org.id == this.organizationId))), + ), + ); + this.exportForm.controls.vaultSelector.patchValue(this.organizationId); + this.exportForm.controls.vaultSelector.disable(); - this.onlyManagedCollections = false; - return; - } + this.onlyManagedCollections = false; + } + // Initialize component to support individual and organizational exports + private initIndividual(): void { this.organizations$ = this.accountService.activeAccount$ .pipe( getUserId, @@ -296,18 +437,18 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { const managedCollectionsOrgIds = new Set( collections.filter((c) => c.manage).map((c) => c.organizationId), ); - // Filter organizations that exist in managedCollectionsOrgIds const filteredOrgs = memberOrganizations.filter((org) => managedCollectionsOrgIds.has(org.id), ); - // Sort the filtered organizations based on the name return filteredOrgs.sort(Utils.getSortFunction(this.i18nService, "name")); }), ); + } + private setupPolicyBasedFormState(): void { combineLatest([ this.disablePersonalVaultExportPolicy$, - this.organizationDataOwnershipPolicy$, + this.organizationDataOwnershipPolicyAppliesToUser$, this.organizations$, ]) .pipe( From 9182628b28272a2204b60e0e7c1c46bdf7f896d3 Mon Sep 17 00:00:00 2001 From: SmithThe4th <gsmith@bitwarden.com> Date: Thu, 2 Oct 2025 11:18:35 -0400 Subject: [PATCH 35/83] Decrypt folder from returned saved folder (#16434) --- apps/cli/src/vault/create.command.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 0892bb42214..03a205e9c48 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -20,6 +20,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { Folder } from "@bitwarden/common/vault/models/domain/folder"; import { KeyService } from "@bitwarden/key-management"; import { OrganizationCollectionRequest } from "../admin-console/models/request/organization-collection.request"; @@ -183,8 +184,8 @@ export class CreateCommand { const userKey = await this.keyService.getUserKey(activeUserId); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { - await this.folderApiService.save(folder, activeUserId); - const newFolder = await this.folderService.get(folder.id, activeUserId); + const folderData = await this.folderApiService.save(folder, activeUserId); + const newFolder = new Folder(folderData); const decFolder = await newFolder.decrypt(); const res = new FolderResponse(decFolder); return Response.success(res); From cac6a3627511204c0855c2a2dfbddb466b3a73ba Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Thu, 2 Oct 2025 14:21:01 -0400 Subject: [PATCH 36/83] [PM-26449] Add mouse cursor hover on the Edit Shortcut link (#16708) * PM-26449 add hover and focus styling for edit shortcut link, add tab support to the link * reduce styling scope, switch from span to appropriate a tag --- apps/desktop/src/app/accounts/settings.component.html | 6 ++++-- apps/desktop/src/scss/misc.scss | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index a0380a8b5ce..7fdca9ff29b 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -351,13 +351,15 @@ <small class="help-block" *ngIf="form.value.enableAutotype"> <b>{{ "important" | i18n }}</b> {{ "enableAutotypeDescriptionTransitionKey" | i18n }} - <span + <a class="settings-link" + tabindex="0" *ngIf="this.form.value.enableAutotype" (click)="saveAutotypeShortcut()" + (keydown.enter)="saveAutotypeShortcut(); $event.preventDefault()" > {{ "editShortcut" | i18n }} - </span></small + </a></small > </div> <div class="form-group"> diff --git a/apps/desktop/src/scss/misc.scss b/apps/desktop/src/scss/misc.scss index b64bdd92120..c70eb823213 100644 --- a/apps/desktop/src/scss/misc.scss +++ b/apps/desktop/src/scss/misc.scss @@ -360,11 +360,16 @@ form, } } -.settings-link { +.help-block a.settings-link { + text-decoration: none; @include themify($themes) { color: themed("primaryColor"); + + &:hover, + &:focus { + color: darken(themed("primaryColor"), 6%); + } } - font-weight: bold; } app-root > #loading, From bbbc10f233778f780eeb2441653de9c154b7af9d Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:34:15 -0500 Subject: [PATCH 37/83] Fix logic for list filtering for trash and archived items (#16702) --- apps/cli/src/commands/list.command.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 49527f6bf78..e5174f67913 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -304,14 +304,14 @@ export class ListCommand { } /** - * Checks if the cipher passes either the trash or the archive options. - * @returns true if the cipher passes *any* of the filters + * Checks if the cipher passes the state filter options. + * @returns true if the cipher matches the requested state */ private matchesStateOptions(c: CipherView, options: Options): boolean { - const passesTrashFilter = options.trash && c.isDeleted; - const passesArchivedFilter = options.archived && c.isArchived; + const passesTrashFilter = options.trash === c.isDeleted; + const passesArchivedFilter = options.archived === c.isArchived; - return passesTrashFilter || passesArchivedFilter; + return passesTrashFilter && passesArchivedFilter; } } From 8315c68567828d1d12f0c8cd51e001656cb61425 Mon Sep 17 00:00:00 2001 From: Matt Gibson <mgibson@bitwarden.com> Date: Thu, 2 Oct 2025 19:58:24 +0000 Subject: [PATCH 38/83] [PM-26318] Limit data.json to current user read/write (#16647) * Limit data.json to current user read/write * Keep existing permissions for portable --- .../services/electron-storage.service.ts | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/apps/desktop/src/platform/services/electron-storage.service.ts b/apps/desktop/src/platform/services/electron-storage.service.ts index 2d292d6537b..34aa8837475 100644 --- a/apps/desktop/src/platform/services/electron-storage.service.ts +++ b/apps/desktop/src/platform/services/electron-storage.service.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import { ipcMain } from "electron"; +import ElectronStore from "electron-store"; import { Subject } from "rxjs"; import { @@ -11,22 +12,7 @@ import { } from "@bitwarden/common/platform/abstractions/storage.service"; import { NodeUtils } from "@bitwarden/node/node-utils"; -// See: https://github.com/sindresorhus/electron-store/blob/main/index.d.ts -interface ElectronStoreOptions { - defaults: unknown; - name: string; -} - -type ElectronStoreConstructor = new (options: ElectronStoreOptions) => ElectronStore; - -// eslint-disable-next-line -const Store: ElectronStoreConstructor = require("electron-store"); - -interface ElectronStore { - get: (key: string) => unknown; - set: (key: string, obj: unknown) => void; - delete: (key: string) => void; -} +import { isWindowsPortable } from "../../utils"; interface BaseOptions<T extends string> { action: T; @@ -48,11 +34,13 @@ export class ElectronStorageService implements AbstractStorageService { if (!fs.existsSync(dir)) { NodeUtils.mkdirpSync(dir, "700"); } - const storeConfig: ElectronStoreOptions = { + const fileMode = isWindowsPortable() ? 0o666 : 0o600; + const storeConfig: ElectronStore.Options<Record<string, unknown>> = { defaults: defaults, name: "data", + configFileMode: fileMode, }; - this.store = new Store(storeConfig); + this.store = new ElectronStore(storeConfig); this.updates$ = this.updatesSubject.asObservable(); ipcMain.handle("storageService", (event, options: Options) => { From 83c457920eac1b7594f5d4171ea9481358de5b8b Mon Sep 17 00:00:00 2001 From: Mark Youssef <141061617+mark-youssef-bitwarden@users.noreply.github.com> Date: Thu, 2 Oct 2025 13:38:09 -0700 Subject: [PATCH 39/83] [PM-14292] Update dialog header to truncate past 2 lines (#15708) * Update dialog header to truncate past 2 lines * Fix case where title is one long word * Fix random white space * Fix one long word to truncate * Switch to break words * Remove pre-line --- .../components/src/dialog/dialog/dialog.component.html | 2 +- libs/components/src/dialog/dialog/dialog.stories.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index 1b701a50584..030879deabd 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -20,7 +20,7 @@ bitDialogTitleContainer bitTypography="h3" noMargin - class="tw-text-main tw-mb-0 tw-truncate" + class="tw-text-main tw-mb-0 tw-line-clamp-2 tw-text-ellipsis tw-break-words" > {{ title() }} @if (subtitle(); as subtitleText) { diff --git a/libs/components/src/dialog/dialog/dialog.stories.ts b/libs/components/src/dialog/dialog/dialog.stories.ts index f93ef1a2f25..d645d32764d 100644 --- a/libs/components/src/dialog/dialog/dialog.stories.ts +++ b/libs/components/src/dialog/dialog/dialog.stories.ts @@ -125,7 +125,15 @@ export const LongTitle: Story = { ...Default, args: { dialogSize: "small", - title: "Long_Title_That_Should_Be_Truncated", + title: "Incredibly_Super_Long_Title_That_Should_Be_Truncated", + }, +}; + +export const LongTitleSentence: Story = { + ...Default, + args: { + dialogSize: "small", + title: "Very Long Sentence That Should Be Truncated After Two Lines", }, }; From 7a38b226675ee9c2ffc6b0c8d5e48df8beecac93 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:39:13 -0700 Subject: [PATCH 40/83] [PM-24951] - update "My Items" icon to bwi-user (#16674) * update "My Items" icon to bwi-user * fix tests * revert changse to reports. fix assign collections. * revert remaining changes to reports --- .../services/vault-popup-list-filters.service.ts | 15 +++++++++++++-- .../filters/collection-filter.component.html | 5 ++++- .../vault-collection-row.component.html | 7 ++++++- .../vault-items/vault-collection-row.component.ts | 8 +++++++- .../vault-filter/services/vault-filter.service.ts | 9 +++++++-- .../vault-header/vault-header.component.ts | 10 +++++++--- .../components/collection-filter.component.ts | 3 ++- .../importer/src/components/import.component.html | 2 +- libs/importer/src/components/import.component.ts | 8 +++++++- .../item-details/item-details-v2.component.ts | 6 ++++-- .../components/assign-collections.component.ts | 8 +++++--- 11 files changed, 63 insertions(+), 18 deletions(-) diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index 05d0ea8d444..08db7d5d4ab 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -14,7 +14,11 @@ import { take, } from "rxjs"; -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { + CollectionService, + CollectionTypes, + CollectionView, +} from "@bitwarden/admin-console/common"; import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { DynamicTreeNode } from "@bitwarden/angular/vault/vault-filter/models/dynamic-tree-node.model"; import { sortDefaultCollections } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service"; @@ -473,7 +477,14 @@ export class VaultPopupListFiltersService { }); }), map((tree) => - tree.nestedList.map((c) => this.convertToChipSelectOption(c, "bwi-collection-shared")), + tree.nestedList.map((c) => + this.convertToChipSelectOption( + c, + c.node.type === CollectionTypes.DefaultUserCollection + ? "bwi-user" + : "bwi-collection-shared", + ), + ), ), shareReplay({ bufferSize: 1, refCount: true }), ); diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html index e123f4400c7..f83a2e1c91f 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html @@ -55,7 +55,10 @@ > <i *ngIf="c.children.length === 0" - class="bwi bwi-fw bwi-collection-shared" + [class]=" + 'bwi bwi-fw ' + + (c.node.type === DefaultCollectionType ? 'bwi-user' : 'bwi-collection-shared') + " aria-hidden="true" ></i>  {{ c.node.name }} diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html index c1955e56903..e351b9f46ce 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html @@ -13,7 +13,12 @@ </td> <td bitCell [ngClass]="RowHeightClass" class="tw-min-w-fit"> <div aria-hidden="true"> - <i class="bwi bwi-fw bwi-lg bwi-collection-shared"></i> + <i + [class]=" + 'bwi bwi-fw bwi-lg ' + + (collection.type === DefaultCollectionType ? 'bwi-user' : 'bwi-collection-shared') + " + ></i> </div> </td> <td bitCell [ngClass]="RowHeightClass"> diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts index 0e69ea3e567..0b6c8423221 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts @@ -2,7 +2,12 @@ // @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { CollectionAdminView, Unassigned, CollectionView } from "@bitwarden/admin-console/common"; +import { + CollectionAdminView, + Unassigned, + CollectionView, + CollectionTypes, +} from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; @@ -26,6 +31,7 @@ export class VaultCollectionRowComponent<C extends CipherViewLike> { protected RowHeightClass = RowHeightClass; protected Unassigned = "unassigned"; protected CollectionPermission = CollectionPermission; + protected DefaultCollectionType = CollectionTypes.DefaultUserCollection; @Input() disabled: boolean; @Input() collection: CollectionView; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index 5897ea8c2ce..1f27773c467 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -12,7 +12,11 @@ import { switchMap, } from "rxjs"; -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { + CollectionService, + CollectionTypes, + CollectionView, +} from "@bitwarden/admin-console/common"; import { sortDefaultCollections } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -257,7 +261,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { const collectionCopy = cloneCollection( new CollectionView({ ...c, name: c.name }), ) as CollectionFilter; - collectionCopy.icon = "bwi-collection-shared"; + collectionCopy.icon = + c.type === CollectionTypes.DefaultUserCollection ? "bwi-user" : "bwi-collection-shared"; const parts = c.name ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, undefined, NestingDelimiter); } diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index aaa3a90aa98..929a8d07881 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -7,6 +7,7 @@ import { Unassigned, CollectionView, CollectionAdminService, + CollectionTypes, } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -152,9 +153,12 @@ export class VaultHeaderComponent { } protected get icon() { - return this.filter?.collectionId && this.filter.collectionId !== All - ? "bwi-collection-shared" - : ""; + if (!this.filter?.collectionId || this.filter.collectionId === All) { + return ""; + } + return this.collection?.node.type === CollectionTypes.DefaultUserCollection + ? "bwi-user" + : "bwi-collection-shared"; } /** diff --git a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts index feaaf74c96d..e9a6923c2fb 100644 --- a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts @@ -4,7 +4,7 @@ import { Directive, EventEmitter, Input, Output } from "@angular/core"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports -import { CollectionView } from "@bitwarden/admin-console/common"; +import { CollectionTypes, CollectionView } from "@bitwarden/admin-console/common"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; import { DynamicTreeNode } from "../models/dynamic-tree-node.model"; @@ -21,6 +21,7 @@ export class CollectionFilterComponent { @Output() onNodeCollapseStateChange: EventEmitter<ITreeNodeObject> = new EventEmitter<ITreeNodeObject>(); @Output() onFilterChange: EventEmitter<VaultFilter> = new EventEmitter<VaultFilter>(); + DefaultCollectionType = CollectionTypes.DefaultUserCollection; readonly collectionsGrouping: TopLevelTreeNode = { id: "collections", diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 9f1247b52da..bca7d15f087 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -49,7 +49,7 @@ *ngFor="let c of collections$ | async" [value]="c" [label]="c.name" - icon="bwi-collection-shared" + [icon]="c.type === DefaultCollectionType ? 'bwi-user' : 'bwi-collection-shared'" /> </ng-container> </bit-select> diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 54c3fc1d408..d98cd817147 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -29,7 +29,11 @@ import { combineLatestWith, filter, map, switchMap, takeUntil } from "rxjs/opera // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { + CollectionService, + CollectionTypes, + CollectionView, +} from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { getOrganizationById, @@ -103,6 +107,8 @@ import { ImportLastPassComponent } from "./lastpass"; providers: ImporterProviders, }) export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { + DefaultCollectionType = CollectionTypes.DefaultUserCollection; + featuredImportOptions: ImportOption[]; importOptions: ImportOption[]; format: ImportType = null; diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts index 6ccd0b7ee61..c775d66baac 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts @@ -7,7 +7,7 @@ import { toSignal } from "@angular/core/rxjs-interop"; import { fromEvent, map, startWith } from "rxjs"; // eslint-disable-next-line no-restricted-imports -import { CollectionView } from "@bitwarden/admin-console/common"; +import { CollectionTypes, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -102,7 +102,9 @@ export class ItemDetailsV2Component { getIconClass(item: Organization | CollectionView | FolderView): string { if (item instanceof CollectionView) { - return "bwi-collection-shared"; + return item.type === CollectionTypes.DefaultUserCollection + ? "bwi-user" + : "bwi-collection-shared"; } else if (item instanceof FolderView) { return "bwi-folder"; } diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 453ba93f380..9890074a8c9 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -330,7 +330,8 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI ); }) .map((c) => ({ - icon: "bwi-collection-shared", + icon: + c.type === CollectionTypes.DefaultUserCollection ? "bwi-user" : "bwi-collection-shared", id: c.id, labelName: c.name, listName: c.name, @@ -371,7 +372,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI collection.id !== this.params.activeCollection?.id, ) .map((collection) => ({ - icon: "bwi-collection-shared", + icon: collection.icon, id: collection.id, labelName: collection.labelName, listName: collection.listName, @@ -435,7 +436,8 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI ) .subscribe((collections) => { this.availableCollections = collections.map((c) => ({ - icon: "bwi-collection-shared", + icon: + c.type === CollectionTypes.DefaultUserCollection ? "bwi-user" : "bwi-collection-shared", id: c.id, labelName: c.name, listName: c.name, From ba6f2b7d82336b9e956544cf67005d219b13e9ee Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:22:09 -0700 Subject: [PATCH 41/83] group collection in getNestedCollectionTree (#16601) --- .../collections/utils/collection-utils.ts | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts index 67cb4c7cdc8..91e9fd4cdcc 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts @@ -5,6 +5,7 @@ import { CollectionView, NestingDelimiter, } from "@bitwarden/admin-console/common"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; @@ -26,15 +27,23 @@ export function getNestedCollectionTree( .sort((a, b) => a.name.localeCompare(b.name)) .map(cloneCollection); - const nodes: TreeNode<CollectionView | CollectionAdminView>[] = []; - clonedCollections.forEach((collection) => { - const parts = - collection.name != null - ? collection.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) - : []; - ServiceUtils.nestedTraverse(nodes, 0, parts, collection, null, NestingDelimiter); + const all: TreeNode<CollectionView | CollectionAdminView>[] = []; + const groupedByOrg = new Map<OrganizationId, CollectionView[]>(); + clonedCollections.map((c) => { + const key = c.organizationId; + (groupedByOrg.get(key) ?? groupedByOrg.set(key, []).get(key)!).push(c); }); - return nodes; + + for (const group of groupedByOrg.values()) { + const nodes: TreeNode<CollectionView>[] = []; + for (const c of group) { + const collectionCopy = Object.assign(new CollectionView({ ...c, name: c.name }), c); + const parts = c.name ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; + ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, undefined, NestingDelimiter); + } + all.push(...nodes); + } + return all; } export function cloneCollection(collection: CollectionView): CollectionView; From fdf47ffe3bd3963c20dd36fab5df1a71edd5fbcc Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:18:47 -0700 Subject: [PATCH 42/83] refactor(login-error): (Auth) [PM-22145] Improved Error State for Failed Login (#16569) Updates the inline error message on a failed login. --- apps/browser/src/_locales/en/messages.json | 9 +++++++++ apps/cli/src/auth/commands/login.command.ts | 10 ++++++++++ apps/cli/src/locales/en/messages.json | 9 +++++++++ apps/desktop/src/locales/en/messages.json | 9 +++++++++ apps/web/src/locales/en/messages.json | 9 +++++++++ libs/auth/src/angular/login/login.component.ts | 11 ++++++++--- 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 771759d9f25..df47d357746 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 133c9658ae7..7e5058dcff0 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -34,6 +34,7 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -369,6 +370,15 @@ export class LoginCommand { return await this.handleSuccessResponse(response); } catch (e) { + if ( + e instanceof ErrorResponse && + e.message === "Username or password is incorrect. Try again." + ) { + const env = await firstValueFrom(this.environmentService.environment$); + const host = Utils.getHost(env.getWebVaultUrl()); + return Response.error(this.i18nService.t("invalidMasterPasswordConfirmEmailAndHost", host)); + } + return Response.error(e); } } diff --git a/apps/cli/src/locales/en/messages.json b/apps/cli/src/locales/en/messages.json index 4a8c774ea42..18079bd2409 100644 --- a/apps/cli/src/locales/en/messages.json +++ b/apps/cli/src/locales/en/messages.json @@ -41,6 +41,15 @@ "invalidMasterPassword": { "message": "Invalid master password." }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "sessionTimeout": { "message": "Your session has timed out. Please go back and try logging in again." }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index b2625bc85f0..8bc0eb9ecc2 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c6901a21824..d3fcad411e0 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 91a421216f2..9ade2c1d0af 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -34,6 +34,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -138,6 +139,7 @@ export class LoginComponent implements OnInit, OnDestroy { private loginSuccessHandlerService: LoginSuccessHandlerService, private configService: ConfigService, private ssoLoginService: SsoLoginServiceAbstraction, + private environmentService: EnvironmentService, ) { this.clientType = this.platformUtilsService.getClientType(); } @@ -307,7 +309,7 @@ export class LoginComponent implements OnInit, OnDestroy { await this.handleAuthResult(authResult); } catch (error) { this.logService.error(error); - this.handleSubmitError(error); + await this.handleSubmitError(error); } }; @@ -316,15 +318,18 @@ export class LoginComponent implements OnInit, OnDestroy { * * @param error The error object. */ - private handleSubmitError(error: unknown) { + private async handleSubmitError(error: unknown) { // Handle error responses if (error instanceof ErrorResponse) { switch (error.statusCode) { case HttpStatusCode.BadRequest: { if (error.message?.toLowerCase().includes("username or password is incorrect")) { + const env = await firstValueFrom(this.environmentService.environment$); + const host = Utils.getHost(env.getWebVaultUrl()); + this.formGroup.controls.masterPassword.setErrors({ error: { - message: this.i18nService.t("invalidMasterPassword"), + message: this.i18nService.t("invalidMasterPasswordConfirmEmailAndHost", host), }, }); } else { From 2ddf1c34b2dec63cb62805d9c00a0e0d174a6c01 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu <acoroiu@bitwarden.com> Date: Fri, 3 Oct 2025 09:01:49 +0200 Subject: [PATCH 43/83] [PM-25488] Badge stays after lock when using pin (#16436) * wip * feat: add dynamic states * feat: re-implement badge service with dynamic state functions * feat: completely remove old static states * feat: debounce calls to badge api per tab * feat: use group-by to avoid re-setting all tabs on 1 tab change * feat: simplify autofill badge updater * feat: add hanging function test * chore: clean up badge service * feat: simplify private updateBadge * feat: remove unnecessary Set usage * fix: tests that broke after setState rename * chore: clean up badge api --- .../auth-status-badge-updater.service.ts | 46 +- .../autofill-badge-updater.service.ts | 81 +- .../browser/src/background/main.background.ts | 2 - .../src/platform/badge/badge-browser-api.ts | 123 +- .../src/platform/badge/badge.service.spec.ts | 1346 ++++++++++------- .../src/platform/badge/badge.service.ts | 213 ++- apps/browser/src/platform/badge/scope.ts | 23 + apps/browser/src/platform/badge/state.ts | 4 +- .../badge/test/mock-badge-browser-api.ts | 59 +- ...-risk-cipher-badge-updater.service.spec.ts | 66 +- .../at-risk-cipher-badge-updater.service.ts | 154 +- libs/state/src/core/state-definitions.ts | 3 - 12 files changed, 1147 insertions(+), 973 deletions(-) create mode 100644 apps/browser/src/platform/badge/scope.ts diff --git a/apps/browser/src/auth/services/auth-status-badge-updater.service.ts b/apps/browser/src/auth/services/auth-status-badge-updater.service.ts index 4205ebc665d..4f239e54939 100644 --- a/apps/browser/src/auth/services/auth-status-badge-updater.service.ts +++ b/apps/browser/src/auth/services/auth-status-badge-updater.service.ts @@ -17,8 +17,8 @@ export class AuthStatusBadgeUpdaterService { private accountService: AccountService, private authService: AuthService, ) { - this.accountService.activeAccount$ - .pipe( + this.badgeService.setState(StateName, (_tab) => + this.accountService.activeAccount$.pipe( switchMap((account) => account ? this.authService.authStatusFor$(account.id) @@ -27,30 +27,36 @@ export class AuthStatusBadgeUpdaterService { mergeMap(async (authStatus) => { switch (authStatus) { case AuthenticationStatus.LoggedOut: { - await this.badgeService.setState(StateName, BadgeStatePriority.High, { - icon: BadgeIcon.LoggedOut, - backgroundColor: Unset, - text: Unset, - }); - break; + return { + priority: BadgeStatePriority.High, + state: { + icon: BadgeIcon.LoggedOut, + backgroundColor: Unset, + text: Unset, + }, + }; } case AuthenticationStatus.Locked: { - await this.badgeService.setState(StateName, BadgeStatePriority.High, { - icon: BadgeIcon.Locked, - backgroundColor: Unset, - text: Unset, - }); - break; + return { + priority: BadgeStatePriority.High, + state: { + icon: BadgeIcon.Locked, + backgroundColor: Unset, + text: Unset, + }, + }; } case AuthenticationStatus.Unlocked: { - await this.badgeService.setState(StateName, BadgeStatePriority.Low, { - icon: BadgeIcon.Unlocked, - }); - break; + return { + priority: BadgeStatePriority.Low, + state: { + icon: BadgeIcon.Unlocked, + }, + }; } } }), - ) - .subscribe(); + ), + ); } } diff --git a/apps/browser/src/autofill/services/autofill-badge-updater.service.ts b/apps/browser/src/autofill/services/autofill-badge-updater.service.ts index 06ddf16c8af..382c9efa7f8 100644 --- a/apps/browser/src/autofill/services/autofill-badge-updater.service.ts +++ b/apps/browser/src/autofill/services/autofill-badge-updater.service.ts @@ -1,4 +1,4 @@ -import { combineLatest, distinctUntilChanged, mergeMap, of, switchMap, withLatestFrom } from "rxjs"; +import { combineLatest, delay, distinctUntilChanged, mergeMap, of, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; @@ -10,7 +10,7 @@ import { Tab } from "../../platform/badge/badge-browser-api"; import { BadgeService } from "../../platform/badge/badge.service"; import { BadgeStatePriority } from "../../platform/badge/priority"; -const StateName = (tabId: number) => `autofill-badge-${tabId}`; +const StateName = "autofill-badge-updater"; export class AutofillBadgeUpdaterService { constructor( @@ -26,56 +26,30 @@ export class AutofillBadgeUpdaterService { switchMap((account) => (account?.id ? this.cipherService.ciphers$(account?.id) : of([]))), ); - // Recalculate badges for all active tabs when ciphers or active account changes - combineLatest({ - account: this.accountService.activeAccount$, - enableBadgeCounter: - this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()), - ciphers: ciphers$, - }) - .pipe( + this.badgeService.setState(StateName, (tab) => { + return combineLatest({ + account: this.accountService.activeAccount$, + enableBadgeCounter: + this.badgeSettingsService.enableBadgeCounter$.pipe(distinctUntilChanged()), + ciphers: ciphers$.pipe(delay(100)), // Delay to allow cipherService.getAllDecryptedForUrl to pick up changes + }).pipe( mergeMap(async ({ account, enableBadgeCounter }) => { - if (!account) { - return; - } - - const tabs = await this.badgeService.getActiveTabs(); - - for (const tab of tabs) { - if (!tab.tabId) { - continue; - } - if (enableBadgeCounter) { - await this.setTabState(tab, account.id); - } else { - await this.clearTabState(tab.tabId); - } - } - }), - ) - .subscribe(); - - // Recalculate badge for a specific tab when it becomes active - this.badgeService.activeTabsUpdated$ - .pipe( - withLatestFrom( - this.accountService.activeAccount$, - this.badgeSettingsService.enableBadgeCounter$, - ), - mergeMap(async ([tabs, account, enableBadgeCounter]) => { if (!account || !enableBadgeCounter) { - return; + return undefined; } - for (const tab of tabs) { - await this.setTabState(tab, account.id); - } + return { + state: { + text: await this.calculateCountText(tab, account.id), + }, + priority: BadgeStatePriority.Default, + }; }), - ) - .subscribe(); + ); + }); } - private async setTabState(tab: Tab, userId: UserId) { + private async calculateCountText(tab: Tab, userId: UserId) { if (!tab.tabId) { this.logService.warning("Tab event received but tab id is undefined"); return; @@ -85,22 +59,9 @@ export class AutofillBadgeUpdaterService { const cipherCount = ciphers.length; if (cipherCount === 0) { - await this.clearTabState(tab.tabId); - return; + return undefined; } - const countText = cipherCount > 9 ? "9+" : cipherCount.toString(); - await this.badgeService.setState( - StateName(tab.tabId), - BadgeStatePriority.Default, - { - text: countText, - }, - tab.tabId, - ); - } - - private async clearTabState(tabId: number) { - await this.badgeService.clearState(StateName(tabId)); + return cipherCount > 9 ? "9+" : cipherCount.toString(); } } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 0820f605a0a..7b62178d237 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1435,7 +1435,6 @@ export default class MainBackground { ); this.badgeService = new BadgeService( - this.stateProvider, new DefaultBadgeBrowserApi(this.platformUtilsService), this.logService, ); @@ -1925,7 +1924,6 @@ export default class MainBackground { this.badgeService, this.accountService, this.cipherService, - this.logService, this.taskService, ); diff --git a/apps/browser/src/platform/badge/badge-browser-api.ts b/apps/browser/src/platform/badge/badge-browser-api.ts index 79b50970400..80f84c3b46e 100644 --- a/apps/browser/src/platform/badge/badge-browser-api.ts +++ b/apps/browser/src/platform/badge/badge-browser-api.ts @@ -1,4 +1,16 @@ -import { concat, defer, filter, map, merge, Observable, shareReplay, switchMap } from "rxjs"; +import { + concat, + concatMap, + defer, + filter, + map, + merge, + Observable, + of, + pairwise, + shareReplay, + switchMap, +} from "rxjs"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -28,13 +40,37 @@ function tabFromChromeTab(tab: chrome.tabs.Tab): Tab { } export interface BadgeBrowserApi { - activeTabsUpdated$: Observable<Tab[]>; + /** + * An observable that emits all currently active tabs whenever one or more active tabs change. + */ + activeTabs$: Observable<Tab[]>; + /** + * An observable that emits tab events such as updates and activations. + */ + tabEvents$: Observable<TabEvent>; + + /** + * Set the badge state for a specific tab. + * If the tabId is undefined the state will be applied to the browser action in general. + */ setState(state: RawBadgeState, tabId?: number): Promise<void>; - getTabs(): Promise<number[]>; - getActiveTabs(): Promise<Tab[]>; } +export type TabEvent = + | { + type: "updated"; + tab: Tab; + } + | { + type: "activated"; + tab: Tab; + } + | { + type: "deactivated"; + tabId: number; + }; + export class DefaultBadgeBrowserApi implements BadgeBrowserApi { private badgeAction = BrowserApi.getBrowserAction(); private sidebarAction = BrowserApi.getSidebarAction(self); @@ -44,18 +80,25 @@ export class DefaultBadgeBrowserApi implements BadgeBrowserApi { shareReplay({ bufferSize: 1, refCount: true }), ); - activeTabsUpdated$ = concat( - defer(async () => await this.getActiveTabs()), + private createdOrUpdatedTabEvents$ = concat( + defer(async () => await this.getActiveTabs()).pipe( + switchMap((activeTabs) => { + const tabEvents: TabEvent[] = activeTabs.map((tab) => ({ + type: "activated", + tab, + })); + return of(...tabEvents); + }), + ), merge( this.onTabActivated$.pipe( - switchMap(async (activeInfo) => { - const tab = await BrowserApi.getTab(activeInfo.tabId); - - if (tab == undefined || tab.id == undefined || tab.url == undefined) { - return []; - } - - return [tabFromChromeTab(tab)]; + switchMap(async (activeInfo) => await BrowserApi.getTab(activeInfo.tabId)), + filter( + (tab): tab is chrome.tabs.Tab => + !(tab == undefined || tab.id == undefined || tab.url == undefined), + ), + switchMap(async (tab) => { + return { type: "activated", tab: tabFromChromeTab(tab) } satisfies TabEvent; }), ), fromChromeEvent(chrome.tabs.onUpdated).pipe( @@ -64,22 +107,58 @@ export class DefaultBadgeBrowserApi implements BadgeBrowserApi { // Only emit if the url was updated changeInfo.url != undefined, ), - map(([_tabId, _changeInfo, tab]) => [tabFromChromeTab(tab)]), + map( + ([_tabId, _changeInfo, tab]) => + ({ type: "updated", tab: tabFromChromeTab(tab) }) satisfies TabEvent, + ), ), fromChromeEvent(chrome.webNavigation.onCommitted).pipe( + filter(([details]) => details.transitionType === "reload"), map(([details]) => { - const toReturn: Tab[] = - details.transitionType === "reload" ? [{ tabId: details.tabId, url: details.url }] : []; - return toReturn; + return { + type: "updated", + tab: { tabId: details.tabId, url: details.url }, + } satisfies TabEvent; }), ), // NOTE: We're only sharing the active tab changes, not the full list of active tabs. // This is so that any new subscriber will get the latest active tabs immediately, but // doesn't re-subscribe to chrome events. ).pipe(shareReplay({ bufferSize: 1, refCount: true })), - ).pipe(filter((tabs) => tabs.length > 0)); + ); - async getActiveTabs(): Promise<Tab[]> { + tabEvents$ = merge( + this.createdOrUpdatedTabEvents$, + this.createdOrUpdatedTabEvents$.pipe( + concatMap(async () => { + return this.getActiveTabs(); + }), + pairwise(), + map(([previousTabs, currentTabs]) => { + const previousTabIds = previousTabs.map((t) => t.tabId); + const currentTabIds = currentTabs.map((t) => t.tabId); + + const deactivatedTabIds = previousTabIds.filter((id) => !currentTabIds.includes(id)); + + return deactivatedTabIds.map( + (tabId) => + ({ + type: "deactivated", + tabId, + }) satisfies TabEvent, + ); + }), + switchMap((events) => of(...events)), + ), + ); + + activeTabs$ = this.tabEvents$.pipe( + concatMap(async () => { + return this.getActiveTabs(); + }), + ); + + private async getActiveTabs(): Promise<Tab[]> { const tabs = await BrowserApi.getActiveTabs(); return tabs.filter((tab) => tab.id != undefined && tab.url != undefined).map(tabFromChromeTab); } @@ -96,10 +175,6 @@ export class DefaultBadgeBrowserApi implements BadgeBrowserApi { ]); } - async getTabs(): Promise<number[]> { - return (await BrowserApi.tabsQuery({})).map((tab) => tab.id).filter((tab) => tab !== undefined); - } - private setIcon(icon: IconPaths, tabId?: number) { return Promise.all([this.setActionIcon(icon, tabId), this.setSidebarActionIcon(icon, tabId)]); } diff --git a/apps/browser/src/platform/badge/badge.service.spec.ts b/apps/browser/src/platform/badge/badge.service.spec.ts index d17e5dc0b5f..815941541e6 100644 --- a/apps/browser/src/platform/badge/badge.service.spec.ts +++ b/apps/browser/src/platform/badge/badge.service.spec.ts @@ -1,11 +1,10 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { Subscription } from "rxjs"; +import { EMPTY, Observable, of, Subscription } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { FakeAccountService, FakeStateProvider } from "@bitwarden/common/spec"; import { RawBadgeState } from "./badge-browser-api"; -import { BadgeService } from "./badge.service"; +import { BadgeService, BadgeStateFunction } from "./badge.service"; import { DefaultBadgeState } from "./consts"; import { BadgeIcon } from "./icon"; import { BadgeStatePriority } from "./priority"; @@ -14,7 +13,6 @@ import { MockBadgeBrowserApi } from "./test/mock-badge-browser-api"; describe("BadgeService", () => { let badgeApi: MockBadgeBrowserApi; - let stateProvider: FakeStateProvider; let logService!: MockProxy<LogService>; let badgeService!: BadgeService; @@ -22,626 +20,834 @@ describe("BadgeService", () => { beforeEach(() => { badgeApi = new MockBadgeBrowserApi(); - stateProvider = new FakeStateProvider(new FakeAccountService({})); logService = mock<LogService>(); - badgeService = new BadgeService(stateProvider, badgeApi, logService); + badgeService = new BadgeService(badgeApi, logService, 0); }); afterEach(() => { badgeServiceSubscription?.unsubscribe(); }); - describe("calling without tabId", () => { - const tabId = 1; - - describe("given a single tab is open", () => { - beforeEach(() => { - badgeApi.tabs = [tabId]; - badgeApi.setActiveTabs([tabId]); - badgeServiceSubscription = badgeService.startListening(); - }); - - it("sets provided state when no other state has been set", async () => { - const state: BadgeState = { - text: "text", - backgroundColor: "color", - icon: BadgeIcon.Locked, - }; - - await badgeService.setState("state-name", BadgeStatePriority.Default, state); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual(state); - }); - - it("sets default values when none are provided", async () => { - // This is a bit of a weird thing to do, but I don't think it's something we need to prohibit - const state: BadgeState = {}; - - await badgeService.setState("state-name", BadgeStatePriority.Default, state); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); - }); - - it("merges states when multiple same-priority states have been set", async () => { - await badgeService.setState("state-1", BadgeStatePriority.Default, { text: "text" }); - await badgeService.setState("state-2", BadgeStatePriority.Default, { - backgroundColor: "#fff", - }); - await badgeService.setState("state-3", BadgeStatePriority.Default, { - icon: BadgeIcon.Locked, - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - const expectedState: RawBadgeState = { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }; - expect(badgeApi.specificStates[tabId]).toEqual(expectedState); - }); - - it("overrides previous lower-priority state when higher-priority state is set", async () => { - await badgeService.setState("state-1", BadgeStatePriority.Low, { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }); - await badgeService.setState("state-2", BadgeStatePriority.Default, { - text: "override", - }); - await badgeService.setState("state-3", BadgeStatePriority.High, { - backgroundColor: "#aaa", - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - const expectedState: RawBadgeState = { - text: "override", - backgroundColor: "#aaa", - icon: BadgeIcon.Locked, - }; - expect(badgeApi.specificStates[tabId]).toEqual(expectedState); - }); - - it("removes override when a previously high-priority state is cleared", async () => { - await badgeService.setState("state-1", BadgeStatePriority.Low, { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }); - await badgeService.setState("state-2", BadgeStatePriority.Default, { - text: "override", - }); - await badgeService.clearState("state-2"); - - await new Promise((resolve) => setTimeout(resolve, 0)); - const expectedState: RawBadgeState = { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }; - expect(badgeApi.specificStates[tabId]).toEqual(expectedState); - }); - - it("sets default values when all states have been cleared", async () => { - await badgeService.setState("state-1", BadgeStatePriority.Low, { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }); - await badgeService.setState("state-2", BadgeStatePriority.Default, { - text: "override", - }); - await badgeService.setState("state-3", BadgeStatePriority.High, { - backgroundColor: "#aaa", - }); - await badgeService.clearState("state-1"); - await badgeService.clearState("state-2"); - await badgeService.clearState("state-3"); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); - }); - - it("sets default value high-priority state contains Unset", async () => { - await badgeService.setState("state-1", BadgeStatePriority.Low, { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }); - await badgeService.setState("state-3", BadgeStatePriority.High, { - icon: Unset, - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - const expectedState: RawBadgeState = { - text: "text", - backgroundColor: "#fff", - icon: DefaultBadgeState.icon, - }; - expect(badgeApi.specificStates[tabId]).toEqual(expectedState); - }); - - it("ignores medium-priority Unset when high-priority contains a value", async () => { - await badgeService.setState("state-1", BadgeStatePriority.Low, { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }); - await badgeService.setState("state-3", BadgeStatePriority.Default, { - icon: Unset, - }); - await badgeService.setState("state-3", BadgeStatePriority.High, { - icon: BadgeIcon.Unlocked, - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - const expectedState: RawBadgeState = { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Unlocked, - }; - expect(badgeApi.specificStates[tabId]).toEqual(expectedState); - }); - }); - - describe("given multiple tabs are open, only one active", () => { - const tabId = 1; - const tabIds = [1, 2, 3]; - - beforeEach(() => { - badgeApi.tabs = tabIds; - badgeApi.setActiveTabs([tabId]); - badgeServiceSubscription = badgeService.startListening(); - }); - - it("sets general state for active tab when no other state has been set", async () => { - const state: BadgeState = { - text: "text", - backgroundColor: "color", - icon: BadgeIcon.Locked, - }; - - await badgeService.setState("state-name", BadgeStatePriority.Default, state); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates).toEqual({ - 1: state, - 2: undefined, - 3: undefined, - }); - }); - - it("only updates the active tab when setting state", async () => { - const state: BadgeState = { - text: "text", - backgroundColor: "color", - icon: BadgeIcon.Locked, - }; - badgeApi.setState.mockReset(); - - await badgeService.setState("state-1", BadgeStatePriority.Default, state, tabId); - await badgeService.setState("state-2", BadgeStatePriority.Default, state, 2); - await badgeService.setState("state-2", BadgeStatePriority.Default, state, 2); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.setState).toHaveBeenCalledTimes(1); - }); - }); - - describe("given multiple tabs are open and multiple are active", () => { - const activeTabIds = [1, 2]; - const tabIds = [1, 2, 3]; - - beforeEach(() => { - badgeApi.tabs = tabIds; - badgeApi.setActiveTabs(activeTabIds); - badgeServiceSubscription = badgeService.startListening(); - }); - - it("sets general state for active tabs when no other state has been set", async () => { - const state: BadgeState = { - text: "text", - backgroundColor: "color", - icon: BadgeIcon.Locked, - }; - - await badgeService.setState("state-name", BadgeStatePriority.Default, state); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates).toEqual({ - 1: state, - 2: state, - 3: undefined, - }); - }); - - it("only updates the active tabs when setting general state", async () => { - const state: BadgeState = { - text: "text", - backgroundColor: "color", - icon: BadgeIcon.Locked, - }; - badgeApi.setState.mockReset(); - - await badgeService.setState("state-1", BadgeStatePriority.Default, state, 1); - await badgeService.setState("state-2", BadgeStatePriority.Default, state, 2); - await badgeService.setState("state-3", BadgeStatePriority.Default, state, 3); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.setState).toHaveBeenCalledTimes(2); - }); - }); - }); - - describe("calling with tabId", () => { - describe("given a single tab is open", () => { + describe("static state", () => { + describe("calling without tabId", () => { const tabId = 1; - beforeEach(() => { - badgeApi.tabs = [tabId]; - badgeApi.setActiveTabs([tabId]); - badgeServiceSubscription = badgeService.startListening(); - }); - - it("sets provided state when no other state has been set", async () => { - const state: BadgeState = { - text: "text", - backgroundColor: "color", - icon: BadgeIcon.Locked, - }; - - await badgeService.setState("state-name", BadgeStatePriority.Default, state, tabId); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual(state); - }); - - it("sets default values when none are provided", async () => { - // This is a bit of a weird thing to do, but I don't think it's something we need to prohibit - const state: BadgeState = {}; - - await badgeService.setState("state-name", BadgeStatePriority.Default, state, tabId); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); - }); - - it("merges tabId specific state with general states", async () => { - await badgeService.setState("general-state", BadgeStatePriority.Default, { text: "text" }); - await badgeService.setState( - "specific-state", - BadgeStatePriority.Default, - { - backgroundColor: "#fff", - }, - tabId, - ); - await badgeService.setState("general-state-2", BadgeStatePriority.Default, { - icon: BadgeIcon.Locked, + describe("given a single tab is open", () => { + beforeEach(() => { + badgeApi.tabs = [tabId]; + badgeApi.setActiveTabs([tabId]); + badgeServiceSubscription = badgeService.startListening(); }); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual({ - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }); - }); - - it("merges states when multiple same-priority states with the same tabId have been set", async () => { - await badgeService.setState("state-1", BadgeStatePriority.Default, { text: "text" }, tabId); - await badgeService.setState( - "state-2", - BadgeStatePriority.Default, - { - backgroundColor: "#fff", - }, - tabId, - ); - await badgeService.setState( - "state-3", - BadgeStatePriority.Default, - { + it("sets provided state when no other state has been set", async () => { + const state: BadgeState = { + text: "text", + backgroundColor: "color", icon: BadgeIcon.Locked, - }, - tabId, - ); + }; - await new Promise((resolve) => setTimeout(resolve, 0)); - const expectedState: RawBadgeState = { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }; - expect(badgeApi.specificStates[tabId]).toEqual(expectedState); - }); + await badgeService.setState( + "state-name", + GeneralStateFunction(BadgeStatePriority.Default, state), + ); - it("overrides previous lower-priority state when higher-priority state with the same tabId is set", async () => { - await badgeService.setState( - "state-1", - BadgeStatePriority.Low, - { + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual(state); + }); + + it("sets default values when none are provided", async () => { + // This is a bit of a weird thing to do, but I don't think it's something we need to prohibit + const state: BadgeState = {}; + + await badgeService.setState( + "state-name", + GeneralStateFunction(BadgeStatePriority.Default, state), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); + }); + + it("sets default values even if state function never emits", async () => { + badgeService.setState("state-name", (_tab) => EMPTY); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); + }); + + it("merges states when multiple same-priority states have been set", async () => { + await badgeService.setState( + "state-1", + GeneralStateFunction(BadgeStatePriority.Default, { text: "text" }), + ); + await badgeService.setState( + "state-2", + GeneralStateFunction(BadgeStatePriority.Default, { + backgroundColor: "#fff", + }), + ); + await badgeService.setState( + "state-3", + GeneralStateFunction(BadgeStatePriority.Default, { + icon: BadgeIcon.Locked, + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedState: RawBadgeState = { text: "text", backgroundColor: "#fff", icon: BadgeIcon.Locked, - }, - tabId, - ); - await badgeService.setState( - "state-2", - BadgeStatePriority.Default, - { + }; + expect(badgeApi.specificStates[tabId]).toEqual(expectedState); + }); + + it("overrides previous lower-priority state when higher-priority state is set", async () => { + await badgeService.setState( + "state-1", + GeneralStateFunction(BadgeStatePriority.Low, { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }), + ); + await badgeService.setState( + "state-2", + GeneralStateFunction(BadgeStatePriority.Default, { + text: "override", + }), + ); + await badgeService.setState( + "state-3", + GeneralStateFunction(BadgeStatePriority.High, { + backgroundColor: "#aaa", + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedState: RawBadgeState = { text: "override", - }, - tabId, - ); - await badgeService.setState( - "state-3", - BadgeStatePriority.High, - { backgroundColor: "#aaa", - }, - tabId, - ); + icon: BadgeIcon.Locked, + }; + expect(badgeApi.specificStates[tabId]).toEqual(expectedState); + }); - await new Promise((resolve) => setTimeout(resolve, 0)); - const expectedState: RawBadgeState = { - text: "override", - backgroundColor: "#aaa", - icon: BadgeIcon.Locked, - }; - expect(badgeApi.specificStates[tabId]).toEqual(expectedState); - }); + it("removes override when a previously high-priority state is cleared", async () => { + await badgeService.setState( + "state-1", + GeneralStateFunction(BadgeStatePriority.Low, { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }), + ); + await badgeService.setState( + "state-2", + GeneralStateFunction(BadgeStatePriority.Default, { + text: "override", + }), + ); + await badgeService.clearState("state-2"); - it("overrides lower-priority tab-specific state when higher-priority general state is set", async () => { - await badgeService.setState( - "state-1", - BadgeStatePriority.Low, - { + await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedState: RawBadgeState = { text: "text", backgroundColor: "#fff", icon: BadgeIcon.Locked, - }, - tabId, - ); - await badgeService.setState("state-2", BadgeStatePriority.Default, { - text: "override", - }); - await badgeService.setState("state-3", BadgeStatePriority.High, { - backgroundColor: "#aaa", + }; + expect(badgeApi.specificStates[tabId]).toEqual(expectedState); }); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual({ - text: "override", - backgroundColor: "#aaa", - icon: BadgeIcon.Locked, - }); - }); + it("sets default values when all states have been cleared", async () => { + await badgeService.setState( + "state-1", + GeneralStateFunction(BadgeStatePriority.Low, { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }), + ); + await badgeService.setState( + "state-2", + GeneralStateFunction(BadgeStatePriority.Default, { + text: "override", + }), + ); + await badgeService.setState( + "state-3", + GeneralStateFunction(BadgeStatePriority.High, { + backgroundColor: "#aaa", + }), + ); + await badgeService.clearState("state-1"); + await badgeService.clearState("state-2"); + await badgeService.clearState("state-3"); - it("removes override when a previously high-priority state with the same tabId is cleared", async () => { - await badgeService.setState( - "state-1", - BadgeStatePriority.Low, - { + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); + }); + + it("sets default value high-priority state contains Unset", async () => { + await badgeService.setState( + "state-1", + GeneralStateFunction(BadgeStatePriority.Low, { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }), + ); + await badgeService.setState( + "state-3", + GeneralStateFunction(BadgeStatePriority.High, { + icon: Unset, + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedState: RawBadgeState = { text: "text", backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }, - tabId, - ); - await badgeService.setState( - "state-2", - BadgeStatePriority.Default, - { - text: "override", - }, - tabId, - ); - await badgeService.clearState("state-2"); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual({ - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, + icon: DefaultBadgeState.icon, + }; + expect(badgeApi.specificStates[tabId]).toEqual(expectedState); }); - }); - it("sets default state when all states with the same tabId have been cleared", async () => { - await badgeService.setState( - "state-1", - BadgeStatePriority.Low, - { + it("ignores medium-priority Unset when high-priority contains a value", async () => { + await badgeService.setState( + "state-1", + GeneralStateFunction(BadgeStatePriority.Low, { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }), + ); + await badgeService.setState( + "state-3", + GeneralStateFunction(BadgeStatePriority.Default, { + icon: Unset, + }), + ); + await badgeService.setState( + "state-3", + GeneralStateFunction(BadgeStatePriority.High, { + icon: BadgeIcon.Unlocked, + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedState: RawBadgeState = { text: "text", backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }, - tabId, - ); - await badgeService.setState( - "state-2", - BadgeStatePriority.Default, - { - text: "override", - }, - tabId, - ); - await badgeService.setState( - "state-3", - BadgeStatePriority.High, - { - backgroundColor: "#aaa", - }, - tabId, - ); - await badgeService.clearState("state-1"); - await badgeService.clearState("state-2"); - await badgeService.clearState("state-3"); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); - }); - - it("sets default value when high-priority state contains Unset", async () => { - await badgeService.setState( - "state-1", - BadgeStatePriority.Low, - { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }, - tabId, - ); - await badgeService.setState( - "state-3", - BadgeStatePriority.High, - { - icon: Unset, - }, - tabId, - ); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual({ - text: "text", - backgroundColor: "#fff", - icon: DefaultBadgeState.icon, - }); - }); - - it("ignores medium-priority Unset when high-priority contains a value", async () => { - await badgeService.setState( - "state-1", - BadgeStatePriority.Low, - { - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Locked, - }, - tabId, - ); - await badgeService.setState( - "state-3", - BadgeStatePriority.Default, - { - icon: Unset, - }, - tabId, - ); - await badgeService.setState( - "state-3", - BadgeStatePriority.High, - { icon: BadgeIcon.Unlocked, - }, - tabId, - ); + }; + expect(badgeApi.specificStates[tabId]).toEqual(expectedState); + }); + }); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates[tabId]).toEqual({ - text: "text", - backgroundColor: "#fff", - icon: BadgeIcon.Unlocked, + describe("given multiple tabs are open, only one active", () => { + const tabId = 1; + const tabIds = [1, 2, 3]; + + beforeEach(() => { + badgeApi.tabs = tabIds; + badgeApi.setActiveTabs([tabId]); + badgeServiceSubscription = badgeService.startListening(); + }); + + it("sets general state for active tab when no other state has been set", async () => { + const state: BadgeState = { + text: "text", + backgroundColor: "color", + icon: BadgeIcon.Locked, + }; + + await badgeService.setState( + "state-name", + GeneralStateFunction(BadgeStatePriority.Default, state), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates).toEqual({ + 1: state, + 2: undefined, + 3: undefined, + }); + }); + + it("only updates the active tab when setting state", async () => { + const state: BadgeState = { + text: "text", + backgroundColor: "color", + icon: BadgeIcon.Locked, + }; + await badgeService.setState( + "state-1", + TabSpecificStateFunction(BadgeStatePriority.Default, state, tabId), + ); + await badgeService.setState( + "state-2", + TabSpecificStateFunction(BadgeStatePriority.Default, state, 2), + ); + await badgeService.setState( + "state-2", + TabSpecificStateFunction(BadgeStatePriority.Default, state, 2), + ); + await new Promise((resolve) => setTimeout(resolve, 0)); + + badgeApi.setState.mockReset(); + badgeApi.updateTab(tabId); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.setState).toHaveBeenCalledTimes(1); + }); + }); + + describe("given multiple tabs are open and multiple are active", () => { + const activeTabIds = [1, 2]; + const tabIds = [1, 2, 3]; + + beforeEach(() => { + badgeApi.tabs = tabIds; + badgeApi.setActiveTabs(activeTabIds); + badgeServiceSubscription = badgeService.startListening(); + }); + + it("sets general state for active tabs when no other state has been set", async () => { + const state: BadgeState = { + text: "text", + backgroundColor: "color", + icon: BadgeIcon.Locked, + }; + + await badgeService.setState( + "state-name", + GeneralStateFunction(BadgeStatePriority.Default, state), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates).toEqual({ + 1: state, + 2: state, + 3: undefined, + }); + }); + + it("only updates the active tabs when setting general state", async () => { + const state: BadgeState = { + text: "text", + backgroundColor: "color", + icon: BadgeIcon.Locked, + }; + + await badgeService.setState( + "state-1", + TabSpecificStateFunction(BadgeStatePriority.Default, state, 1), + ); + await badgeService.setState( + "state-2", + TabSpecificStateFunction(BadgeStatePriority.Default, state, 2), + ); + await badgeService.setState( + "state-3", + TabSpecificStateFunction(BadgeStatePriority.Default, state, 3), + ); + await new Promise((resolve) => setTimeout(resolve, 0)); + + badgeApi.setState.mockReset(); + badgeApi.updateTab(activeTabIds[0]); + badgeApi.updateTab(activeTabIds[1]); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.setState).toHaveBeenCalledTimes(2); }); }); }); - describe("given multiple tabs are open, only one active", () => { - const tabId = 1; - const tabIds = [1, 2, 3]; + describe("setting tab-specific states", () => { + describe("given a single tab is open", () => { + const tabId = 1; - beforeEach(() => { - badgeApi.tabs = tabIds; - badgeApi.setActiveTabs([tabId]); - badgeServiceSubscription = badgeService.startListening(); - }); - - it("sets tab-specific state for provided tab", async () => { - const generalState: BadgeState = { - text: "general-text", - backgroundColor: "general-color", - icon: BadgeIcon.Unlocked, - }; - const specificState: BadgeState = { - text: "tab-text", - icon: BadgeIcon.Locked, - }; - - await badgeService.setState("general-state", BadgeStatePriority.Default, generalState); - await badgeService.setState( - "tab-state", - BadgeStatePriority.Default, - specificState, - tabIds[0], - ); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates).toEqual({ - [tabIds[0]]: { ...specificState, backgroundColor: "general-color" }, - [tabIds[1]]: undefined, - [tabIds[2]]: undefined, + beforeEach(() => { + badgeApi.tabs = [tabId]; + badgeApi.setActiveTabs([tabId]); + badgeServiceSubscription = badgeService.startListening(); }); - }); - }); - describe("given multiple tabs are open and multiple are active", () => { - const activeTabIds = [1, 2]; - const tabIds = [1, 2, 3]; + it("sets provided state when no other state has been set", async () => { + const state: BadgeState = { + text: "text", + backgroundColor: "color", + icon: BadgeIcon.Locked, + }; - beforeEach(() => { - badgeApi.tabs = tabIds; - badgeApi.setActiveTabs(activeTabIds); - badgeServiceSubscription = badgeService.startListening(); - }); + await badgeService.setState( + "state-name", + TabSpecificStateFunction(BadgeStatePriority.Default, state, tabId), + ); - it("sets general state for all active tabs when no other state has been set", async () => { - const generalState: BadgeState = { - text: "general-text", - backgroundColor: "general-color", - icon: BadgeIcon.Unlocked, - }; + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual(state); + }); - await badgeService.setState("general-state", BadgeStatePriority.Default, generalState); + it("sets default values when none are provided", async () => { + // This is a bit of a weird thing to do, but I don't think it's something we need to prohibit + const state: BadgeState = {}; - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates).toEqual({ - [tabIds[0]]: generalState, - [tabIds[1]]: generalState, - [tabIds[2]]: undefined, + await badgeService.setState( + "state-name", + TabSpecificStateFunction(BadgeStatePriority.Default, state, tabId), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); + }); + + it("merges tabId specific state with general states", async () => { + await badgeService.setState( + "general-state", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + text: "text", + }, + tabId, + ), + ); + await badgeService.setState( + "specific-state", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + backgroundColor: "#fff", + }, + tabId, + ), + ); + await badgeService.setState( + "general-state-2", + GeneralStateFunction(BadgeStatePriority.Default, { + icon: BadgeIcon.Locked, + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual({ + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }); + }); + + it("merges states when multiple same-priority states with the same tabId have been set", async () => { + await badgeService.setState( + "state-1", + TabSpecificStateFunction(BadgeStatePriority.Default, { text: "text" }, tabId), + ); + await badgeService.setState( + "state-2", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + backgroundColor: "#fff", + }, + tabId, + ), + ); + await badgeService.setState( + "state-3", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + icon: BadgeIcon.Locked, + }, + tabId, + ), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedState: RawBadgeState = { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }; + expect(badgeApi.specificStates[tabId]).toEqual(expectedState); + }); + + it("overrides previous lower-priority state when higher-priority state with the same tabId is set", async () => { + await badgeService.setState( + "state-1", + TabSpecificStateFunction( + BadgeStatePriority.Low, + { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }, + tabId, + ), + ); + await badgeService.setState( + "state-2", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + text: "override", + }, + tabId, + ), + ); + await badgeService.setState( + "state-3", + TabSpecificStateFunction( + BadgeStatePriority.High, + { + backgroundColor: "#aaa", + }, + tabId, + ), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + const expectedState: RawBadgeState = { + text: "override", + backgroundColor: "#aaa", + icon: BadgeIcon.Locked, + }; + expect(badgeApi.specificStates[tabId]).toEqual(expectedState); + }); + + it("overrides lower-priority tab-specific state when higher-priority general state is set", async () => { + await badgeService.setState( + "state-1", + TabSpecificStateFunction( + BadgeStatePriority.Low, + { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }, + tabId, + ), + ); + await badgeService.setState( + "state-2", + GeneralStateFunction(BadgeStatePriority.Default, { + text: "override", + }), + ); + await badgeService.setState( + "state-3", + GeneralStateFunction(BadgeStatePriority.High, { + backgroundColor: "#aaa", + }), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual({ + text: "override", + backgroundColor: "#aaa", + icon: BadgeIcon.Locked, + }); + }); + + it("removes override when a previously high-priority state with the same tabId is cleared", async () => { + await badgeService.setState( + "state-1", + TabSpecificStateFunction( + BadgeStatePriority.Low, + { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }, + tabId, + ), + ); + await badgeService.setState( + "state-2", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + text: "override", + }, + tabId, + ), + ); + await badgeService.clearState("state-2"); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual({ + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }); + }); + + it("sets default state when all states with the same tabId have been cleared", async () => { + await badgeService.setState( + "state-1", + TabSpecificStateFunction( + BadgeStatePriority.Low, + { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }, + tabId, + ), + ); + await badgeService.setState( + "state-2", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + text: "override", + }, + tabId, + ), + ); + await badgeService.setState( + "state-3", + TabSpecificStateFunction( + BadgeStatePriority.High, + { + backgroundColor: "#aaa", + }, + tabId, + ), + ); + await badgeService.clearState("state-1"); + await badgeService.clearState("state-2"); + await badgeService.clearState("state-3"); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual(DefaultBadgeState); + }); + + it("sets default value when high-priority state contains Unset", async () => { + await badgeService.setState( + "state-1", + TabSpecificStateFunction( + BadgeStatePriority.Low, + { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }, + tabId, + ), + ); + await badgeService.setState( + "state-3", + TabSpecificStateFunction( + BadgeStatePriority.High, + { + icon: Unset, + }, + tabId, + ), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual({ + text: "text", + backgroundColor: "#fff", + icon: DefaultBadgeState.icon, + }); + }); + + it("ignores medium-priority Unset when high-priority contains a value", async () => { + await badgeService.setState( + "state-1", + TabSpecificStateFunction( + BadgeStatePriority.Low, + { + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Locked, + }, + tabId, + ), + ); + await badgeService.setState( + "state-3", + TabSpecificStateFunction( + BadgeStatePriority.Default, + { + icon: Unset, + }, + tabId, + ), + ); + await badgeService.setState( + "state-3", + TabSpecificStateFunction( + BadgeStatePriority.High, + { + icon: BadgeIcon.Unlocked, + }, + tabId, + ), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates[tabId]).toEqual({ + text: "text", + backgroundColor: "#fff", + icon: BadgeIcon.Unlocked, + }); }); }); - it("sets tab-specific state for provided tab", async () => { - const generalState: BadgeState = { - text: "general-text", - backgroundColor: "general-color", - icon: BadgeIcon.Unlocked, - }; - const specificState: BadgeState = { - text: "tab-text", - icon: BadgeIcon.Locked, - }; + describe("given multiple tabs are open, only one active", () => { + const tabId = 1; + const tabIds = [1, 2, 3]; - await badgeService.setState("general-state", BadgeStatePriority.Default, generalState); - await badgeService.setState( - "tab-state", - BadgeStatePriority.Default, - specificState, - tabIds[0], - ); + beforeEach(() => { + badgeApi.tabs = tabIds; + badgeApi.setActiveTabs([tabId]); + badgeServiceSubscription = badgeService.startListening(); + }); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(badgeApi.specificStates).toEqual({ - [tabIds[0]]: { ...specificState, backgroundColor: "general-color" }, - [tabIds[1]]: generalState, - [tabIds[2]]: undefined, + it("sets tab-specific state for provided tab", async () => { + const generalState: BadgeState = { + text: "general-text", + backgroundColor: "general-color", + icon: BadgeIcon.Unlocked, + }; + const specificState: BadgeState = { + text: "tab-text", + icon: BadgeIcon.Locked, + }; + + await badgeService.setState( + "general-state", + GeneralStateFunction(BadgeStatePriority.Default, generalState), + ); + await badgeService.setState( + "tab-state", + TabSpecificStateFunction(BadgeStatePriority.Default, specificState, tabIds[0]), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates).toEqual({ + [tabIds[0]]: { ...specificState, backgroundColor: "general-color" }, + [tabIds[1]]: undefined, + [tabIds[2]]: undefined, + }); + }); + }); + + describe("given multiple tabs are open and multiple are active", () => { + const activeTabIds = [1, 2]; + const tabIds = [1, 2, 3]; + + beforeEach(() => { + badgeApi.tabs = tabIds; + badgeApi.setActiveTabs(activeTabIds); + badgeServiceSubscription = badgeService.startListening(); + }); + + it("sets general state for all active tabs when no other state has been set", async () => { + const generalState: BadgeState = { + text: "general-text", + backgroundColor: "general-color", + icon: BadgeIcon.Unlocked, + }; + + await badgeService.setState( + "general-state", + GeneralStateFunction(BadgeStatePriority.Default, generalState), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates).toEqual({ + [tabIds[0]]: generalState, + [tabIds[1]]: generalState, + [tabIds[2]]: undefined, + }); + }); + + it("sets tab-specific state for provided tab", async () => { + const generalState: BadgeState = { + text: "general-text", + backgroundColor: "general-color", + icon: BadgeIcon.Unlocked, + }; + const specificState: BadgeState = { + text: "tab-text", + icon: BadgeIcon.Locked, + }; + + await badgeService.setState( + "general-state", + GeneralStateFunction(BadgeStatePriority.Default, generalState), + ); + await badgeService.setState( + "tab-state", + TabSpecificStateFunction(BadgeStatePriority.Default, specificState, tabIds[0]), + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(badgeApi.specificStates).toEqual({ + [tabIds[0]]: { ...specificState, backgroundColor: "general-color" }, + [tabIds[1]]: generalState, + [tabIds[2]]: undefined, + }); + }); + + it("unsubscribes from state function when tab is deactivated", async () => { + let subscriptions = 0; + badgeService.setState("state", (tab) => { + return new Observable(() => { + subscriptions++; + return () => { + subscriptions--; + }; + }); + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(subscriptions).toBe(activeTabIds.length); + + badgeApi.deactivateTab(activeTabIds[0]); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(subscriptions).toBe(activeTabIds.length - 1); }); }); }); }); }); + +/** + * Creates a dynamic state function that only provides a state for a specific tab. + */ +function TabSpecificStateFunction( + priority: BadgeStatePriority, + state: BadgeState, + tabId: number, +): BadgeStateFunction { + return (tab) => { + if (tab.tabId === tabId) { + return of({ + priority, + state, + }); + } + + return EMPTY; + }; +} + +/** + * Creates a dynamic state function that provides the same state for all tabs. + */ +function GeneralStateFunction(priority: BadgeStatePriority, state: BadgeState): BadgeStateFunction { + return (_tab) => + of({ + priority, + state, + }); +} diff --git a/apps/browser/src/platform/badge/badge.service.ts b/apps/browser/src/platform/badge/badge.service.ts index 5634aabec28..f6d799b2a80 100644 --- a/apps/browser/src/platform/badge/badge.service.ts +++ b/apps/browser/src/platform/badge/badge.service.ts @@ -1,69 +1,100 @@ -import { concatMap, filter, Subscription, withLatestFrom } from "rxjs"; +import { + BehaviorSubject, + combineLatest, + combineLatestWith, + concatMap, + debounceTime, + filter, + groupBy, + map, + mergeMap, + Observable, + of, + startWith, + Subscription, + switchMap, + takeUntil, +} from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { - BADGE_MEMORY, - GlobalState, - KeyDefinition, - StateProvider, -} from "@bitwarden/common/platform/state"; import { BadgeBrowserApi, RawBadgeState, Tab } from "./badge-browser-api"; import { DefaultBadgeState } from "./consts"; import { BadgeStatePriority } from "./priority"; import { BadgeState, Unset } from "./state"; -interface StateSetting { +const BADGE_UPDATE_DEBOUNCE_MS = 100; + +export interface BadgeStateSetting { priority: BadgeStatePriority; state: BadgeState; - tabId?: number; } -const BADGE_STATES = new KeyDefinition(BADGE_MEMORY, "badgeStates", { - deserializer: (value: Record<string, StateSetting>) => value ?? {}, - cleanupDelayMs: 0, -}); +/** + * A function that returns the badge state for a specific tab. + * Return `undefined` to clear any previously set state for the tab. + */ +export type BadgeStateFunction = (tab: Tab) => Observable<BadgeStateSetting | undefined>; export class BadgeService { - private serviceState: GlobalState<Record<string, StateSetting>>; - - /** - * An observable that emits whenever one or multiple tabs are updated and might need its state updated. - * Use this to know exactly which tabs to calculate the badge state for. - * This is not the same as `onActivated` which only emits when the active tab changes. - */ - activeTabsUpdated$ = this.badgeApi.activeTabsUpdated$; - - getActiveTabs(): Promise<Tab[]> { - return this.badgeApi.getActiveTabs(); - } + private stateFunctions = new BehaviorSubject<Record<string, BadgeStateFunction>>({}); constructor( - private stateProvider: StateProvider, private badgeApi: BadgeBrowserApi, private logService: LogService, - ) { - this.serviceState = this.stateProvider.getGlobal(BADGE_STATES); - } + private debounceTimeMs: number = BADGE_UPDATE_DEBOUNCE_MS, + ) {} /** * Start listening for badge state changes. * Without this the service will not be able to update the badge state. */ startListening(): Subscription { - // React to tab changes - return this.badgeApi.activeTabsUpdated$ + // Default state function that always returns an empty state with lowest priority. + // This will ensure that there is always at least one state to consider when calculating the final badge state, + // so that the badge is cleared/set to default when no other states are set. + const defaultTabStateFunction: BadgeStateFunction = (_tab) => + of({ + priority: BadgeStatePriority.Low, + state: {}, + }); + + return this.badgeApi.tabEvents$ .pipe( - withLatestFrom(this.serviceState.state$), - filter(([activeTabs]) => activeTabs.length > 0), - concatMap(async ([activeTabs, serviceState]) => { - await Promise.all(activeTabs.map((tab) => this.updateBadge(serviceState, tab.tabId))); + groupBy((event) => (event.type === "deactivated" ? event.tabId : event.tab.tabId), { + duration: (group$) => + // Allow clean up of group when deactivated event arrives for this tabId + group$.pipe(filter((evt) => evt.type === "deactivated")), + }), + mergeMap((group$) => + group$.pipe( + // ignore deactivation events, only handle updates/activations + filter((evt) => evt.type !== "deactivated"), + map((evt) => evt.tab), + combineLatestWith(this.stateFunctions), + switchMap(([tab, dynamicStateFunctions]) => { + const functions = [...Object.values(dynamicStateFunctions), defaultTabStateFunction]; + + return combineLatest(functions.map((f) => f(tab).pipe(startWith(undefined)))).pipe( + map((states) => ({ + tab, + states: states.filter((s): s is BadgeStateSetting => s !== undefined), + })), + debounceTime(this.debounceTimeMs), + ); + }), + takeUntil(group$.pipe(filter((evt) => evt.type === "deactivated"))), + ), + ), + + concatMap(async (tabUpdate) => { + await this.updateBadge(tabUpdate.states, tabUpdate.tab.tabId); }), ) .subscribe({ error: (error: unknown) => { this.logService.error( - "Fatal error in badge service observable, badge will fail to update", + "BadgeService: Fatal error updating badge state. Badge will no longer be updated.", error, ); }, @@ -71,68 +102,45 @@ export class BadgeService { } /** - * Inform badge service of a new state that the badge should reflect. + * Register a function that takes an observable of active tab updates and returns an observable of state settings. + * This can be used to create dynamic badge states that react to tab changes. + * The returned observable should emit a new state setting whenever the badge state should be updated. * - * This will merge the new state with any existing states: + * This will merge all states: * - If the new state has a higher priority, it will override any lower priority states. * - If the new state has a lower priority, it will be ignored. * - If the name of the state is already in use, it will be updated. * - If the state has a `tabId` set, it will only apply to that tab. * - States with `tabId` can still be overridden by states without `tabId` if they have a higher priority. - * - * @param name The name of the state. This is used to identify the state and will be used to clear it later. - * @param priority The priority of the state (higher numbers are higher priority, but setting arbitrary numbers is not supported). - * @param state The state to set. - * @param tabId Limit this badge state to a specific tab. If this is not set, the state will be applied to all tabs. */ - async setState(name: string, priority: BadgeStatePriority, state: BadgeState, tabId?: number) { - const newServiceState = await this.serviceState.update((s) => ({ - ...s, - [name]: { priority, state, tabId }, - })); - await this.updateBadge(newServiceState, tabId); + setState(name: string, stateFunction: BadgeStateFunction) { + this.stateFunctions.next({ + ...this.stateFunctions.value, + [name]: stateFunction, + }); } /** - * Clear the state with the given name. + * Clear a state function previously registered with `setState`. * - * This will remove the state from the badge service and clear it from the badge. - * If the state is not found, nothing will happen. + * This will: + * - Stop the function from being called on future tab changes + * - Unsubscribe from any existing observables created by the function. + * - Clear any badge state previously set by the function. * - * @param name The name of the state to clear. + * @param name The name of the state function to clear. */ - async clearState(name: string) { - let clearedState: StateSetting | undefined; - - const newServiceState = await this.serviceState.update((s) => { - clearedState = s?.[name]; - - const newStates = { ...s }; - delete newStates[name]; - return newStates; - }); - - if (clearedState === undefined) { - return; - } - await this.updateBadge(newServiceState, clearedState.tabId); + clearState(name: string) { + const currentDynamicStateFunctions = this.stateFunctions.value; + const newDynamicStateFunctions = { ...currentDynamicStateFunctions }; + delete newDynamicStateFunctions[name]; + this.stateFunctions.next(newDynamicStateFunctions); } - private calculateState(states: Set<StateSetting>, tabId?: number): RawBadgeState { - const sortedStates = [...states].sort((a, b) => a.priority - b.priority); + private calculateState(states: BadgeStateSetting[]): RawBadgeState { + const sortedStates = states.sort((a, b) => a.priority - b.priority); - let filteredStates = sortedStates; - if (tabId !== undefined) { - // Filter out states that are not applicable to the current tab. - // If a state has no tabId, it is considered applicable to all tabs. - // If a state has a tabId, it is only applicable to that tab. - filteredStates = sortedStates.filter((s) => s.tabId === tabId || s.tabId === undefined); - } else { - // If no tabId is provided, we only want states that are not tab-specific. - filteredStates = sortedStates.filter((s) => s.tabId === undefined); - } - - const mergedState = filteredStates + const mergedState = sortedStates .map((s) => s.state) .reduce<Partial<RawBadgeState>>((acc: Partial<RawBadgeState>, state: BadgeState) => { const newState = { ...acc }; @@ -156,43 +164,16 @@ export class BadgeService { * This will only update the badge if the active tab is the same as the tabId of the latest change. * If the active tab is not set, it will not update the badge. * - * @param activeTab The currently active tab. * @param serviceState The current state of the badge service. If this is null or undefined, an empty set will be assumed. * @param tabId Tab id for which the the latest state change applied to. Set this to activeTab.tabId to force an update. + * @param activeTabs The currently active tabs. If not provided, it will be fetched from the badge API. */ - private async updateBadge( - serviceState: Record<string, StateSetting> | null | undefined, - tabId: number | undefined, - ) { - const activeTabs = await this.badgeApi.getActiveTabs(); - if (tabId !== undefined && !activeTabs.some((tab) => tab.tabId === tabId)) { - return; // No need to update the badge if the state is not for the active tab. - } - - const tabIdsToUpdate = tabId ? [tabId] : activeTabs.map((tab) => tab.tabId); - - for (const tabId of tabIdsToUpdate) { - if (tabId === undefined) { - continue; // Skip if tab id is undefined. - } - - const newBadgeState = this.calculateState(new Set(Object.values(serviceState ?? {})), tabId); - try { - await this.badgeApi.setState(newBadgeState, tabId); - } catch (error) { - this.logService.error("Failed to set badge state", error); - } - } - - if (tabId === undefined) { - // If no tabId was provided we should also update the general badge state - const newBadgeState = this.calculateState(new Set(Object.values(serviceState ?? {}))); - - try { - await this.badgeApi.setState(newBadgeState, tabId); - } catch (error) { - this.logService.error("Failed to set general badge state", error); - } + private async updateBadge(serviceState: BadgeStateSetting[], tabId: number) { + const newBadgeState = this.calculateState(serviceState); + try { + await this.badgeApi.setState(newBadgeState, tabId); + } catch (error) { + this.logService.error("Failed to set badge state", error); } } } diff --git a/apps/browser/src/platform/badge/scope.ts b/apps/browser/src/platform/badge/scope.ts new file mode 100644 index 00000000000..5d6cb8dd4e7 --- /dev/null +++ b/apps/browser/src/platform/badge/scope.ts @@ -0,0 +1,23 @@ +export const BadgeStateScope = { + /** + * The state is global and applies to all users. + */ + Global: { type: "global" } satisfies BadgeStateScope, + /** + * The state is for a specific user and only applies to that user when they are unlocked. + */ + UserUnlocked: (userId: string) => + ({ + type: "user_unlocked", + userId, + }) satisfies BadgeStateScope, +} as const; + +export type BadgeStateScope = + | { + type: "global"; + } + | { + type: "user_unlocked"; + userId: string; + }; diff --git a/apps/browser/src/platform/badge/state.ts b/apps/browser/src/platform/badge/state.ts index 0731ad81f41..ea6b52b28e4 100644 --- a/apps/browser/src/platform/badge/state.ts +++ b/apps/browser/src/platform/badge/state.ts @@ -1,6 +1,8 @@ import { BadgeIcon } from "./icon"; -export const Unset = Symbol("Unset badge state"); +const UnsetValue = Symbol("Unset badge state"); + +export const Unset = UnsetValue as typeof UnsetValue; export type Unset = typeof Unset; export type BadgeState = { diff --git a/apps/browser/src/platform/badge/test/mock-badge-browser-api.ts b/apps/browser/src/platform/badge/test/mock-badge-browser-api.ts index a1b79c29cb8..9f8db3f23ef 100644 --- a/apps/browser/src/platform/badge/test/mock-badge-browser-api.ts +++ b/apps/browser/src/platform/badge/test/mock-badge-browser-api.ts @@ -1,33 +1,50 @@ -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, concat, defer, of, Subject, switchMap } from "rxjs"; -import { BadgeBrowserApi, RawBadgeState, Tab } from "../badge-browser-api"; +import { BadgeBrowserApi, RawBadgeState, Tab, TabEvent } from "../badge-browser-api"; export class MockBadgeBrowserApi implements BadgeBrowserApi { - private _activeTabsUpdated$ = new BehaviorSubject<Tab[]>([]); - activeTabsUpdated$ = this._activeTabsUpdated$.asObservable(); + private _activeTabs$ = new BehaviorSubject<Tab[]>([]); + private _tabEvents$ = new Subject<TabEvent>(); + activeTabs$ = this._activeTabs$.asObservable(); specificStates: Record<number, RawBadgeState> = {}; generalState?: RawBadgeState; tabs: number[] = []; - activeTabs: number[] = []; - getActiveTabs(): Promise<Tab[]> { - return Promise.resolve( - this.activeTabs.map( - (tabId) => - ({ - tabId, - url: `https://example.com/${tabId}`, - }) satisfies Tab, - ), - ); + tabEvents$ = concat( + defer(() => [this.activeTabs]).pipe( + switchMap((activeTabs) => { + const tabEvents: TabEvent[] = activeTabs.map((tab) => ({ + type: "activated", + tab, + })); + return of(...tabEvents); + }), + ), + this._tabEvents$.asObservable(), + ); + + get activeTabs() { + return this._activeTabs$.value; } setActiveTabs(tabs: number[]) { - this.activeTabs = tabs; - this._activeTabsUpdated$.next( - tabs.map((tabId) => ({ tabId, url: `https://example.com/${tabId}` })), - ); + this._activeTabs$.next(tabs.map((tabId) => ({ tabId, url: `https://example.com/${tabId}` }))); + + tabs.forEach((tabId) => { + this._tabEvents$.next({ + type: "activated", + tab: { tabId, url: `https://example.com/${tabId}` }, + }); + }); + } + + updateTab(tabId: number) { + this._tabEvents$.next({ type: "updated", tab: { tabId, url: `https://example.com/${tabId}` } }); + } + + deactivateTab(tabId: number) { + this._tabEvents$.next({ type: "deactivated", tabId }); } setState = jest.fn().mockImplementation((state: RawBadgeState, tabId?: number): Promise<void> => { @@ -39,8 +56,4 @@ export class MockBadgeBrowserApi implements BadgeBrowserApi { return Promise.resolve(); }); - - getTabs(): Promise<number[]> { - return Promise.resolve(this.tabs); - } } diff --git a/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.spec.ts b/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.spec.ts index f2567ef4267..b84d17a8375 100644 --- a/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.spec.ts +++ b/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.spec.ts @@ -1,12 +1,11 @@ -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SecurityTask, TaskService } from "@bitwarden/common/vault/tasks"; -import { LogService } from "@bitwarden/logging"; -import { UserId } from "@bitwarden/user-core"; +import { SecurityTask, SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks"; -import { BadgeService } from "../../platform/badge/badge.service"; +import { Tab } from "../../platform/badge/badge-browser-api"; +import { BadgeService, BadgeStateFunction } from "../../platform/badge/badge.service"; import { BadgeIcon } from "../../platform/badge/icon"; import { BadgeStatePriority } from "../../platform/badge/priority"; import { Unset } from "../../platform/badge/state"; @@ -18,34 +17,32 @@ describe("AtRiskCipherBadgeUpdaterService", () => { let service: AtRiskCipherBadgeUpdaterService; let setState: jest.Mock; - let clearState: jest.Mock; - let warning: jest.Mock; let getAllDecryptedForUrl: jest.Mock; let getTab: jest.Mock; let addListener: jest.Mock; - const activeAccount$ = new BehaviorSubject({ id: "test-account-id" }); - const cipherViews$ = new BehaviorSubject([]); - const pendingTasks$ = new BehaviorSubject<SecurityTask[]>([]); - const userId = "test-user-id" as UserId; + let activeAccount$: BehaviorSubject<{ id: string }>; + let cipherViews$: BehaviorSubject<Array<{ id: string; isDeleted?: boolean }>>; + let pendingTasks$: BehaviorSubject<SecurityTask[]>; beforeEach(async () => { setState = jest.fn().mockResolvedValue(undefined); - clearState = jest.fn().mockResolvedValue(undefined); - warning = jest.fn(); getAllDecryptedForUrl = jest.fn().mockResolvedValue([]); getTab = jest.fn(); addListener = jest.fn(); + activeAccount$ = new BehaviorSubject({ id: "test-account-id" }); + cipherViews$ = new BehaviorSubject<Array<{ id: string; isDeleted?: boolean }>>([]); + pendingTasks$ = new BehaviorSubject<SecurityTask[]>([]); + jest.spyOn(BrowserApi, "addListener").mockImplementation(addListener); jest.spyOn(BrowserApi, "getTab").mockImplementation(getTab); service = new AtRiskCipherBadgeUpdaterService( - { setState, clearState } as unknown as BadgeService, + { setState } as unknown as BadgeService, { activeAccount$ } as unknown as AccountService, - { cipherViews$, getAllDecryptedForUrl } as unknown as CipherService, - { warning } as unknown as LogService, - { pendingTasks$ } as unknown as TaskService, + { cipherViews$: () => cipherViews$, getAllDecryptedForUrl } as unknown as CipherService, + { pendingTasks$: () => pendingTasks$ } as unknown as TaskService, ); await service.init(); @@ -55,30 +52,41 @@ describe("AtRiskCipherBadgeUpdaterService", () => { jest.restoreAllMocks(); }); + it("registers dynamic state function on init", () => { + expect(setState).toHaveBeenCalledWith("at-risk-cipher-badge", expect.any(Function)); + }); + it("clears the tab state when there are no ciphers and no pending tasks", async () => { - const tab = { id: 1 } as chrome.tabs.Tab; + const tab: Tab = { tabId: 1, url: "https://bitwarden.com" }; + const stateFunction = setState.mock.calls[0][1]; - await service["setTabState"](tab, userId, []); + const state = await firstValueFrom(stateFunction(tab)); - expect(clearState).toHaveBeenCalledWith("at-risk-cipher-badge-1"); + expect(state).toBeUndefined(); }); it("sets state when there are pending tasks for the tab", async () => { - const tab = { id: 3, url: "https://bitwarden.com" } as chrome.tabs.Tab; - const pendingTasks: SecurityTask[] = [{ id: "task1", cipherId: "cipher1" } as SecurityTask]; + const tab: Tab = { tabId: 3, url: "https://bitwarden.com" }; + const stateFunction: BadgeStateFunction = setState.mock.calls[0][1]; + const pendingTasks: SecurityTask[] = [ + { + id: "task1", + cipherId: "cipher1", + type: SecurityTaskType.UpdateAtRiskCredential, + } as SecurityTask, + ]; + pendingTasks$.next(pendingTasks); getAllDecryptedForUrl.mockResolvedValueOnce([{ id: "cipher1" }]); - await service["setTabState"](tab, userId, pendingTasks); + const state = await firstValueFrom(stateFunction(tab)); - expect(setState).toHaveBeenCalledWith( - "at-risk-cipher-badge-3", - BadgeStatePriority.High, - { + expect(state).toEqual({ + priority: BadgeStatePriority.High, + state: { icon: BadgeIcon.Berry, text: Unset, backgroundColor: Unset, }, - 3, - ); + }); }); }); diff --git a/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.ts b/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.ts index 47364958ad8..a06c208ebe2 100644 --- a/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.ts +++ b/apps/browser/src/vault/services/at-risk-cipher-badge-updater.service.ts @@ -1,26 +1,18 @@ -import { combineLatest, map, mergeMap, of, Subject, switchMap } from "rxjs"; +import { combineLatest, concatMap, map, of, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SecurityTask, SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks"; +import { SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks"; import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; import { BadgeService } from "../../platform/badge/badge.service"; import { BadgeIcon } from "../../platform/badge/icon"; import { BadgeStatePriority } from "../../platform/badge/priority"; import { Unset } from "../../platform/badge/state"; -import { BrowserApi } from "../../platform/browser/browser-api"; -const StateName = (tabId: number) => `at-risk-cipher-badge-${tabId}`; +const StateName = "at-risk-cipher-badge"; export class AtRiskCipherBadgeUpdaterService { - private tabReplaced$ = new Subject<{ addedTab: chrome.tabs.Tab; removedTabId: number }>(); - private tabUpdated$ = new Subject<chrome.tabs.Tab>(); - private tabRemoved$ = new Subject<number>(); - private tabActivated$ = new Subject<chrome.tabs.Tab>(); - private activeUserData$ = this.accountService.activeAccount$.pipe( filterOutNullish(), switchMap((user) => @@ -40,124 +32,36 @@ export class AtRiskCipherBadgeUpdaterService { private badgeService: BadgeService, private accountService: AccountService, private cipherService: CipherService, - private logService: LogService, private taskService: TaskService, - ) { - combineLatest({ - replaced: this.tabReplaced$, - activeUserData: this.activeUserData$, - }) - .pipe( - mergeMap(async ({ replaced, activeUserData: [userId, pendingTasks] }) => { - await this.clearTabState(replaced.removedTabId); - await this.setTabState(replaced.addedTab, userId, pendingTasks); - }), - ) - .subscribe(() => {}); - - combineLatest({ - tab: this.tabActivated$, - activeUserData: this.activeUserData$, - }) - .pipe( - mergeMap(async ({ tab, activeUserData: [userId, pendingTasks] }) => { - await this.setTabState(tab, userId, pendingTasks); - }), - ) - .subscribe(); - - combineLatest({ - tab: this.tabUpdated$, - activeUserData: this.activeUserData$, - }) - .pipe( - mergeMap(async ({ tab, activeUserData: [userId, pendingTasks] }) => { - await this.setTabState(tab, userId, pendingTasks); - }), - ) - .subscribe(); - - this.tabRemoved$ - .pipe( - mergeMap(async (tabId) => { - await this.clearTabState(tabId); - }), - ) - .subscribe(); - } + ) {} init() { - BrowserApi.addListener(chrome.tabs.onReplaced, async (addedTabId, removedTabId) => { - const newTab = await BrowserApi.getTab(addedTabId); - if (!newTab) { - this.logService.warning( - `Tab replaced event received but new tab not found (id: ${addedTabId})`, - ); - return; - } + this.badgeService.setState(StateName, (tab) => { + return this.activeUserData$.pipe( + concatMap(async ([userId, pendingTasks]) => { + const ciphers = tab.url + ? await this.cipherService.getAllDecryptedForUrl(tab.url, userId, [], undefined, true) + : []; - this.tabReplaced$.next({ - removedTabId, - addedTab: newTab, - }); + const hasPendingTasksForTab = pendingTasks.some((task) => + ciphers.some((cipher) => cipher.id === task.cipherId && !cipher.isDeleted), + ); + + if (!hasPendingTasksForTab) { + return undefined; + } + + return { + priority: BadgeStatePriority.High, + state: { + icon: BadgeIcon.Berry, + // Unset text and background color to use default badge appearance + text: Unset, + backgroundColor: Unset, + }, + }; + }), + ); }); - - BrowserApi.addListener(chrome.tabs.onUpdated, (_, changeInfo, tab) => { - if (changeInfo.url) { - this.tabUpdated$.next(tab); - } - }); - - BrowserApi.addListener(chrome.tabs.onActivated, async (activeInfo) => { - const tab = await BrowserApi.getTab(activeInfo.tabId); - if (!tab) { - this.logService.warning( - `Tab activated event received but tab not found (id: ${activeInfo.tabId})`, - ); - return; - } - - this.tabActivated$.next(tab); - }); - - BrowserApi.addListener(chrome.tabs.onRemoved, (tabId, _) => this.tabRemoved$.next(tabId)); - } - - /** Sets the pending task state for the tab */ - private async setTabState(tab: chrome.tabs.Tab, userId: UserId, pendingTasks: SecurityTask[]) { - if (!tab.id) { - this.logService.warning("Tab event received but tab id is undefined"); - return; - } - - const ciphers = tab.url - ? await this.cipherService.getAllDecryptedForUrl(tab.url, userId, [], undefined, true) - : []; - - const hasPendingTasksForTab = pendingTasks.some((task) => - ciphers.some((cipher) => cipher.id === task.cipherId && !cipher.isDeleted), - ); - - if (!hasPendingTasksForTab) { - await this.clearTabState(tab.id); - return; - } - - await this.badgeService.setState( - StateName(tab.id), - BadgeStatePriority.High, - { - icon: BadgeIcon.Berry, - // Unset text and background color to use default badge appearance - text: Unset, - backgroundColor: Unset, - }, - tab.id, - ); - } - - /** Clears the pending task state from a tab */ - private async clearTabState(tabId: number) { - await this.badgeService.clearState(StateName(tabId)); } } diff --git a/libs/state/src/core/state-definitions.ts b/libs/state/src/core/state-definitions.ts index e3ffa457e10..1c09b071e99 100644 --- a/libs/state/src/core/state-definitions.ts +++ b/libs/state/src/core/state-definitions.ts @@ -111,9 +111,6 @@ export const NEW_WEB_LAYOUT_BANNER_DISK = new StateDefinition("newWebLayoutBanne export const APPLICATION_ID_DISK = new StateDefinition("applicationId", "disk", { web: "disk-local", }); -export const BADGE_MEMORY = new StateDefinition("badge", "memory", { - browser: "memory-large-object", -}); export const BIOMETRIC_SETTINGS_DISK = new StateDefinition("biometricSettings", "disk"); export const CLEAR_EVENT_DISK = new StateDefinition("clearEvent", "disk"); export const CONFIG_DISK = new StateDefinition("config", "disk", { From cb20889a94eb56c7f49dfb1c3d874faf1f06280a Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Fri, 3 Oct 2025 03:52:00 -0400 Subject: [PATCH 44/83] [SM-1489] machine account event logs (#15997) * Adding enums for additional event logs for secrets * updating messages * Updating messages to be consistent for logs * Displaying project logs, and fixing search query param searching in projects list, having deleted log for secrets and projects not show as a link * Viewing secret and project event logs in event modal, adding to the context menu for secrets and projects the ability to view the logs if user has permission. Restricting logs to SM projs and Secs if the logged in user has event log access but not SM access. * lint * Lint Fixes * fix to messages file * fixing lint * Adding machine account event logs * lint fix * Update event.service.ts * removing duplicate function issue from merge * Update service-accounts-list.component.ts * fixing message * Fixes to QA bugs * lint fix * linter for messages is annoying * lint --- .../manage/entity-events.component.ts | 10 +- apps/web/src/app/core/event.service.ts | 104 +++++++++++++++++- apps/web/src/locales/en/messages.json | 86 ++++++++++++++- .../service-accounts-list.component.html | 9 ++ .../service-accounts-list.component.ts | 62 ++++++++++- .../service-accounts.component.ts | 85 ++++++++++++-- libs/common/src/abstractions/api.service.ts | 7 ++ libs/common/src/enums/event-type.enum.ts | 7 ++ .../src/models/response/event.response.ts | 2 + libs/common/src/services/api.service.ts | 22 ++++ 10 files changed, 375 insertions(+), 19 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 8484b05283d..b4c5a273ac7 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -28,7 +28,7 @@ import { EventService } from "../../../core"; import { SharedModule } from "../../../shared"; export interface EntityEventsDialogParams { - entity: "user" | "cipher" | "secret" | "project"; + entity: "user" | "cipher" | "secret" | "project" | "service-account"; entityId: string; organizationId?: string; @@ -174,6 +174,14 @@ export class EntityEventsComponent implements OnInit, OnDestroy { dates[1], clearExisting ? null : this.continuationToken, ); + } else if (this.params.entity === "service-account") { + response = await this.apiService.getEventsServiceAccount( + this.params.organizationId, + this.params.entityId, + dates[0], + dates[1], + clearExisting ? null : this.continuationToken, + ); } else if (this.params.entity === "project") { response = await this.apiService.getEventsProject( this.params.organizationId, diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 7b1e598a77e..05a7f5aa64c 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -559,9 +559,12 @@ export class EventService { humanReadableMsg = this.i18nService.t("editedSecretWithId", this.getShortId(ev.secretId)); break; case EventType.Project_Retrieved: - msg = this.i18nService.t("accessedProjectWithId", this.formatProjectId(ev, options)); + msg = this.i18nService.t( + "accessedProjectWithIdentifier", + this.formatProjectId(ev, options), + ); humanReadableMsg = this.i18nService.t( - "accessedProjectWithId", + "accessedProjectWithIdentifier", this.getShortId(ev.projectId), ); break; @@ -583,6 +586,74 @@ export class EventService { msg = this.i18nService.t("editedProjectWithId", this.formatProjectId(ev, options)); humanReadableMsg = this.i18nService.t("editedProjectWithId", this.getShortId(ev.projectId)); break; + case EventType.ServiceAccount_UserAdded: + msg = this.i18nService.t( + "addedUserToServiceAccountWithId", + this.formatUserId(ev, options), + this.formatServiceAccountId(ev, options), + ); + humanReadableMsg = this.i18nService.t( + "addedUserToServiceAccountWithId", + this.formatUserId(ev, options), + this.formatServiceAccountId(ev, options), + ); + break; + case EventType.ServiceAccount_UserRemoved: + msg = this.i18nService.t( + "removedUserToServiceAccountWithId", + this.formatUserId(ev, options), + this.formatServiceAccountId(ev, options), + ); + humanReadableMsg = this.i18nService.t( + "removedUserToServiceAccountWithId", + this.formatUserId(ev, options), + this.formatServiceAccountId(ev, options), + ); + break; + case EventType.ServiceAccount_GroupRemoved: + msg = this.i18nService.t( + "removedGroupFromServiceAccountWithId", + this.formatGroupId(ev), + this.formatServiceAccountId(ev, options), + ); + humanReadableMsg = this.i18nService.t( + "removedGroupFromServiceAccountWithId", + this.formatGroupId(ev), + this.formatServiceAccountId(ev, options), + ); + break; + case EventType.ServiceAccount_GroupAdded: + msg = this.i18nService.t( + "addedGroupToServiceAccountId", + this.formatGroupId(ev), + this.formatServiceAccountId(ev, options), + ); + humanReadableMsg = this.i18nService.t( + "addedGroupToServiceAccountId", + this.formatGroupId(ev), + this.formatServiceAccountId(ev, options), + ); + break; + case EventType.ServiceAccount_Created: + msg = this.i18nService.t( + "serviceAccountCreatedWithId", + this.formatServiceAccountId(ev, options), + ); + humanReadableMsg = this.i18nService.t( + "serviceAccountCreatedWithId", + this.formatServiceAccountId(ev, options), + ); + break; + case EventType.ServiceAccount_Deleted: + msg = this.i18nService.t( + "serviceAccountDeletedWithId", + this.formatServiceAccountId(ev, options), + ); + humanReadableMsg = this.i18nService.t( + "serviceAccountDeletedWithId", + this.formatServiceAccountId(ev, options), + ); + break; default: break; } @@ -757,6 +828,35 @@ export class EventService { return a.outerHTML; } + formatServiceAccountId(ev: EventResponse, options: EventOptions): string { + const shortId = this.getShortId(ev.grantedServiceAccountId); + if (options.disableLink) { + return shortId; + } + const a = this.makeAnchor(shortId); + a.setAttribute( + "href", + "#/sm/" + + ev.organizationId + + "/machine-accounts?search=" + + shortId + + "&viewEvents=" + + ev.grantedServiceAccountId + + "&type=all", + ); + return a.outerHTML; + } + + formatUserId(ev: EventResponse, options: EventOptions): string { + const shortId = this.getShortId(ev.userId); + if (options.disableLink) { + return shortId; + } + const a = this.makeAnchor(shortId); + a.setAttribute("href", "#/organizations/" + ev.organizationId + "/members?search=" + shortId); + return a.outerHTML; + } + formatProjectId(ev: EventResponse, options: EventOptions): string { const shortId = this.getShortId(ev.projectId); if (options.disableLink) { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index d3fcad411e0..9b26ec271c0 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7219,6 +7219,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8569,8 +8572,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8595,6 +8598,15 @@ "example": "4d34e8a8" } } + }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", @@ -8604,6 +8616,76 @@ "example": "4d34e8a8" } } + }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", 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 3d7fc9715c3..f2fb49b73f4 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 @@ -84,6 +84,15 @@ <i class="bwi bwi-fw bwi-pencil" aria-hidden="true"></i> {{ "editMachineAccount" | i18n }} </button> + <button + type="button" + bitMenuItem + *ngIf="viewEventsAllowed$ | async as allowed" + (click)="openEventsDialog(serviceAccount)" + > + <i class="bwi bwi-fw bwi-billing" aria-hidden="true"></i> + <span> {{ "viewEvents" | i18n }} </span> + </button> <button type="button" bitMenuItem (click)="delete(serviceAccount)"> <i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i> <span class="tw-text-danger"> diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts index ac3defaf5dd..21f11d6bfed 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts @@ -1,11 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { SelectionModel } from "@angular/cdk/collections"; -import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core"; -import { Subject, takeUntil } from "rxjs"; +import { Component, EventEmitter, Input, OnDestroy, Output, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { catchError, concatMap, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { TableDataSource, ToastService } from "@bitwarden/components"; +import { DialogRef, DialogService, TableDataSource, ToastService } from "@bitwarden/components"; +import { LogService } from "@bitwarden/logging"; +import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component"; import { ServiceAccountSecretsDetailsView, @@ -17,7 +26,7 @@ import { templateUrl: "./service-accounts-list.component.html", standalone: false, }) -export class ServiceAccountsListComponent implements OnDestroy { +export class ServiceAccountsListComponent implements OnDestroy, OnInit { protected dataSource = new TableDataSource<ServiceAccountSecretsDetailsView>(); @Input() @@ -43,18 +52,52 @@ export class ServiceAccountsListComponent implements OnDestroy { @Output() editServiceAccountEvent = new EventEmitter<string>(); private destroy$: Subject<void> = new Subject<void>(); - + protected viewEventsAllowed$: Observable<boolean>; + protected isAdmin$: Observable<boolean>; selection = new SelectionModel<string>(true, []); constructor( private i18nService: I18nService, private toastService: ToastService, + private dialogService: DialogService, + private organizationService: OrganizationService, + private activatedRoute: ActivatedRoute, + private accountService: AccountService, + private logService: LogService, ) { this.selection.changed .pipe(takeUntil(this.destroy$)) .subscribe((_) => this.onServiceAccountCheckedEvent.emit(this.selection.selected)); } + ngOnInit(): void { + this.viewEventsAllowed$ = this.activatedRoute.params.pipe( + concatMap((params) => + getUserId(this.accountService.activeAccount$).pipe( + switchMap((userId) => + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ), + ), + ), + map((org) => org.canAccessEventLogs), + catchError((error: unknown) => { + if (typeof error === "string") { + this.toastService.showToast({ + message: error, + variant: "error", + title: "", + }); + } else { + this.logService.error(error); + } + return of(false); + }), + takeUntil(this.destroy$), + ); + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); @@ -94,4 +137,13 @@ export class ServiceAccountsListComponent implements OnDestroy { }); } } + openEventsDialog = (serviceAccount: ServiceAccountView): DialogRef<void> => + openEntityEventsDialog(this.dialogService, { + data: { + name: serviceAccount.name, + organizationId: serviceAccount.organizationId, + entityId: serviceAccount.id, + entity: "service-account", + }, + }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts index 2813ece001f..345fff03876 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { combineLatest, firstValueFrom, Observable, startWith, switchMap } from "rxjs"; +import { ActivatedRoute, Router } from "@angular/router"; +import { combineLatestWith, firstValueFrom, Observable, startWith, switchMap } from "rxjs"; import { getOrganizationById, @@ -10,7 +10,9 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { DialogService } from "@bitwarden/components"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; +import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component"; import { ServiceAccountSecretsDetailsView, @@ -46,14 +48,16 @@ export class ServiceAccountsComponent implements OnInit { private serviceAccountService: ServiceAccountService, private organizationService: OrganizationService, private accountService: AccountService, + private toastService: ToastService, + private router: Router, + private i18nService: I18nService, ) {} ngOnInit() { - this.serviceAccounts$ = combineLatest([ - this.route.params, - this.serviceAccountService.serviceAccount$.pipe(startWith(null)), - ]).pipe( - switchMap(async ([params]) => { + this.serviceAccounts$ = this.serviceAccountService.serviceAccount$.pipe( + startWith(null), + combineLatestWith(this.route.params), + switchMap(async ([_, params]) => { this.organizationId = params.organizationId; const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.organizationEnabled = ( @@ -64,11 +68,74 @@ export class ServiceAccountsComponent implements OnInit { ) )?.enabled; - return await this.getServiceAccounts(); + const serviceAccounts = await this.getServiceAccounts(); + + const viewEvents = this.route.snapshot.queryParams.viewEvents; + if (viewEvents) { + const targetAccount = serviceAccounts.find((sa) => sa.id === viewEvents); + + const userIsAdmin = ( + await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ) + )?.isAdmin; + + if (!targetAccount) { + if (userIsAdmin) { + this.openEventsDialogByEntityId( + this.i18nService.t("nameUnavailableServiceAccountDeleted", viewEvents), + viewEvents, + ); + } else { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("unknownServiceAccount"), + }); + } + } else { + this.openEventsDialog(targetAccount); + } + + await this.router.navigate([], { + queryParams: { search: this.search }, + }); + } + + return serviceAccounts; }), ); + + if (this.route.snapshot.queryParams.search) { + this.search = this.route.snapshot.queryParams.search; + } } + openEventsDialogByEntityId = ( + serviceAccountName: string, + serviceAccountId: string, + ): DialogRef<void> => + openEntityEventsDialog(this.dialogService, { + data: { + name: serviceAccountName, + organizationId: this.organizationId, + entityId: serviceAccountId, + entity: "service-account", + }, + }); + + openEventsDialog = (serviceAccount: ServiceAccountView): DialogRef<void> => + openEntityEventsDialog(this.dialogService, { + data: { + name: serviceAccount.name, + organizationId: this.organizationId, + entityId: serviceAccount.id, + entity: "service-account", + }, + }); + openNewServiceAccountDialog() { this.dialogService.open<unknown, ServiceAccountOperation>(ServiceAccountDialogComponent, { data: { diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 28ab0613e14..aea52d7310d 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -461,6 +461,13 @@ export abstract class ApiService { end: string, token: string, ): Promise<ListResponse<EventResponse>>; + abstract getEventsServiceAccount( + orgId: string, + id: string, + start: string, + end: string, + token: string, + ): Promise<ListResponse<EventResponse>>; abstract getEventsProject( orgId: string, id: string, diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts index f6a48fe23bb..b3b12118ede 100644 --- a/libs/common/src/enums/event-type.enum.ts +++ b/libs/common/src/enums/event-type.enum.ts @@ -108,4 +108,11 @@ export enum EventType { Project_Created = 2201, Project_Edited = 2202, Project_Deleted = 2203, + + ServiceAccount_UserAdded = 2300, + ServiceAccount_UserRemoved = 2301, + ServiceAccount_GroupAdded = 2302, + ServiceAccount_GroupRemoved = 2303, + ServiceAccount_Created = 2304, + ServiceAccount_Deleted = 2305, } diff --git a/libs/common/src/models/response/event.response.ts b/libs/common/src/models/response/event.response.ts index 07124b3080c..41376e4086f 100644 --- a/libs/common/src/models/response/event.response.ts +++ b/libs/common/src/models/response/event.response.ts @@ -24,6 +24,7 @@ export class EventResponse extends BaseResponse { secretId: string; projectId: string; serviceAccountId: string; + grantedServiceAccountId: string; constructor(response: any) { super(response); @@ -48,5 +49,6 @@ export class EventResponse extends BaseResponse { this.secretId = this.getResponseProperty("SecretId"); this.projectId = this.getResponseProperty("ProjectId"); this.serviceAccountId = this.getResponseProperty("ServiceAccountId"); + this.grantedServiceAccountId = this.getResponseProperty("GrantedServiceAccountId"); } } diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index b10df69e277..f000f35f126 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1272,6 +1272,28 @@ export class ApiService implements ApiServiceAbstraction { return new ListResponse(r, EventResponse); } + async getEventsServiceAccount( + orgId: string, + id: string, + start: string, + end: string, + token: string, + ): Promise<ListResponse<EventResponse>> { + const r = await this.send( + "GET", + this.addEventParameters( + "/organization/" + orgId + "/service-account/" + id + "/events", + start, + end, + token, + ), + null, + true, + true, + ); + return new ListResponse(r, EventResponse); + } + async getEventsProject( orgId: string, id: string, From a1e226b598b0eb2423aa8da965c09be5705c8f14 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:51:25 +0200 Subject: [PATCH 45/83] [PM-25603] Use angular template for avatar (#15978) We should be able to simplify the angular component by using svg element directly. --- .../current-account.component.html | 1 - apps/browser/src/popup/scss/pages.scss | 5 + libs/common/src/platform/misc/utils.ts | 2 +- .../components/src/avatar/avatar.component.ts | 141 +++++++----------- 4 files changed, 63 insertions(+), 86 deletions(-) diff --git a/apps/browser/src/auth/popup/account-switching/current-account.component.html b/apps/browser/src/auth/popup/account-switching/current-account.component.html index c16abdadf29..2e2440f6258 100644 --- a/apps/browser/src/auth/popup/account-switching/current-account.component.html +++ b/apps/browser/src/auth/popup/account-switching/current-account.component.html @@ -12,7 +12,6 @@ [color]="currentAccount.avatarColor" size="small" aria-hidden="true" - class="[&>img]:tw-block" ></bit-avatar> </button> <ng-template #defaultButton> diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss index 4c2daab2159..56c5f80c86c 100644 --- a/apps/browser/src/popup/scss/pages.scss +++ b/apps/browser/src/popup/scss/pages.scss @@ -137,3 +137,8 @@ body.body-full { margin-bottom: 0; } } + +/** Temporary fix for avatar, will not be required once we migrate to tailwind preflight **/ +bit-avatar svg { + display: block; +} diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index 43a9e43d92b..c771bee5463 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -453,7 +453,7 @@ export class Utils { }; } - static isNullOrWhitespace(str: string): boolean { + static isNullOrWhitespace(str: string | null | undefined): boolean { return str == null || typeof str !== "string" || str.trim() === ""; } diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 6f83c9ca101..8ece033c73d 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -1,6 +1,5 @@ import { NgClass } from "@angular/common"; -import { Component, OnChanges, input } from "@angular/core"; -import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; +import { Component, computed, input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -22,12 +21,34 @@ const SizeClasses: Record<SizeTypes, string[]> = { */ @Component({ selector: "bit-avatar", - template: `@if (src) { - <img [src]="src" title="{{ title() || text() }}" [ngClass]="classList" /> - }`, + template: ` + <span [title]="title() || text()"> + <svg + xmlns="http://www.w3.org/2000/svg" + pointer-events="none" + [style.backgroundColor]="backgroundColor()" + [ngClass]="classList()" + attr.viewBox="0 0 {{ svgSize }} {{ svgSize }}" + > + <text + text-anchor="middle" + y="50%" + x="50%" + dy="0.35em" + pointer-events="auto" + [attr.fill]="textColor()" + [style.fontWeight]="svgFontWeight" + [style.fontSize.px]="svgFontSize" + font-family='Roboto,"Helvetica Neue",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"' + > + {{ displayChars() }} + </text> + </svg> + </span> + `, imports: [NgClass], }) -export class AvatarComponent implements OnChanges { +export class AvatarComponent { readonly border = input(false); readonly color = input<string>(); readonly id = input<string>(); @@ -35,36 +56,40 @@ export class AvatarComponent implements OnChanges { readonly title = input<string>(); readonly size = input<SizeTypes>("default"); - private svgCharCount = 2; - private svgFontSize = 20; - private svgFontWeight = 300; - private svgSize = 48; - src?: SafeResourceUrl; + protected readonly svgCharCount = 2; + protected readonly svgFontSize = 20; + protected readonly svgFontWeight = 300; + protected readonly svgSize = 48; - constructor(public sanitizer: DomSanitizer) {} - - ngOnChanges() { - this.generate(); - } - - get classList() { - return ["tw-rounded-full", "tw-inline"] + protected readonly classList = computed(() => { + return ["tw-rounded-full"] .concat(SizeClasses[this.size()] ?? []) .concat(this.border() ? ["tw-border", "tw-border-solid", "tw-border-secondary-600"] : []); - } + }); - private generate() { - const color = this.color(); - const text = this.text(); + protected readonly backgroundColor = computed(() => { const id = this.id(); - if (!text && !color && !id) { - throw new Error("Must supply `text`, `color`, or `id` input."); + const upperCaseText = this.text()?.toUpperCase() ?? ""; + + if (!Utils.isNullOrWhitespace(this.color())) { + return this.color()!; } - let chars: string | null = null; - const upperCaseText = text?.toUpperCase() ?? ""; - chars = this.getFirstLetters(upperCaseText, this.svgCharCount); + if (!Utils.isNullOrWhitespace(id)) { + return Utils.stringToColor(id!.toString()); + } + return Utils.stringToColor(upperCaseText); + }); + + protected readonly textColor = computed(() => { + return Utils.pickTextColorBasedOnBgColor(this.backgroundColor(), 135, true); + }); + + protected readonly displayChars = computed(() => { + const upperCaseText = this.text()?.toUpperCase() ?? ""; + + let chars = this.getFirstLetters(upperCaseText, this.svgCharCount); if (chars == null) { chars = this.unicodeSafeSubstring(upperCaseText, this.svgCharCount); } @@ -75,30 +100,10 @@ export class AvatarComponent implements OnChanges { chars = emojiMatch[0]; } - let svg: HTMLElement; - let hexColor = color ?? ""; - if (!Utils.isNullOrWhitespace(hexColor)) { - svg = this.createSvgElement(this.svgSize, hexColor); - } else if (!Utils.isNullOrWhitespace(id ?? "")) { - hexColor = Utils.stringToColor(id!.toString()); - svg = this.createSvgElement(this.svgSize, hexColor); - } else { - hexColor = Utils.stringToColor(upperCaseText); - svg = this.createSvgElement(this.svgSize, hexColor); - } + return chars; + }); - const charObj = this.createTextElement(chars, hexColor); - svg.appendChild(charObj); - const html = window.document.createElement("div").appendChild(svg).outerHTML; - const svgHtml = window.btoa(unescape(encodeURIComponent(html))); - - // This is safe because the only user provided value, chars is set using `textContent` - this.src = this.sanitizer.bypassSecurityTrustResourceUrl( - "data:image/svg+xml;base64," + svgHtml, - ); - } - - private getFirstLetters(data: string, count: number): string | null { + private getFirstLetters(data: string, count: number): string | undefined { const parts = data.split(" "); if (parts.length > 1) { let text = ""; @@ -107,39 +112,7 @@ export class AvatarComponent implements OnChanges { } return text; } - return null; - } - - private createSvgElement(size: number, color: string): HTMLElement { - const svgTag = window.document.createElement("svg"); - svgTag.setAttribute("xmlns", "http://www.w3.org/2000/svg"); - svgTag.setAttribute("pointer-events", "none"); - svgTag.setAttribute("width", size.toString()); - svgTag.setAttribute("height", size.toString()); - svgTag.style.backgroundColor = color; - svgTag.style.width = size + "px"; - svgTag.style.height = size + "px"; - return svgTag; - } - - private createTextElement(character: string, color: string): HTMLElement { - const textTag = window.document.createElement("text"); - textTag.setAttribute("text-anchor", "middle"); - textTag.setAttribute("y", "50%"); - textTag.setAttribute("x", "50%"); - textTag.setAttribute("dy", "0.35em"); - textTag.setAttribute("pointer-events", "auto"); - textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true)); - textTag.setAttribute( - "font-family", - 'Roboto,"Helvetica Neue",Helvetica,Arial,' + - 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"', - ); - // Warning do not use innerHTML here, characters are user provided - textTag.textContent = character; - textTag.style.fontWeight = this.svgFontWeight.toString(); - textTag.style.fontSize = this.svgFontSize + "px"; - return textTag; + return undefined; } private unicodeSafeSubstring(str: string, count: number) { From f7a3ad88058068a89f99f158249a8bf4573d9c2b Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:17:27 +0200 Subject: [PATCH 46/83] Autosync the updated translations (#16715) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ar/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/az/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/be/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/bg/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/bn/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/bs/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ca/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/cs/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/cy/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/da/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/de/messages.json | 37 ++++++++++++++++--- apps/desktop/src/locales/el/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/en_GB/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/en_IN/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/eo/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/es/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/et/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/eu/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/fa/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/fi/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/fil/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/fr/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/gl/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/he/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/hi/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/hr/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/hu/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/id/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/it/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ja/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ka/messages.json | 33 +++++++++++++++-- apps/desktop/src/locales/km/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/kn/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ko/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/lt/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/lv/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/me/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ml/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/mr/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/my/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/nb/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ne/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/nl/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/nn/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/or/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/pl/messages.json | 33 +++++++++++++++-- apps/desktop/src/locales/pt_BR/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/pt_PT/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/ro/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/ru/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/si/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/sk/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/sl/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/sr/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/sv/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/ta/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/te/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/th/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/tr/messages.json | 31 +++++++++++++++- apps/desktop/src/locales/uk/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/vi/messages.json | 39 +++++++++++++++++--- apps/desktop/src/locales/zh_CN/messages.json | 27 ++++++++++++++ apps/desktop/src/locales/zh_TW/messages.json | 27 ++++++++++++++ 64 files changed, 1763 insertions(+), 35 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 128a2e81864..5da05806006 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Ongeldige hoofwagwoord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Tweestapsaantekening maak u rekening veiliger deur u aantekenpoging te bevestig met ’n ander toestel soos ’n beveiligingsleutel, waarmerktoep, SMS, telefoonoproep of e-pos. U kan tweestapsaantekening in die webkluis op bitwarden.com aktiveer. Wil u die webwerf nou besoek?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Vergrendel" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 3dacae24389..2b1c70f30b6 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "كلمة المرور الرئيسية غير صالحة" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "تسجيل الدخول بخطوتين يجعل حسابك أكثر أمنا من خلال مطالبتك بالتحقق من تسجيل الدخول باستخدام جهاز آخر مثل مفتاح الأمان، تطبيق المصادقة، الرسائل القصيرة، المكالمة الهاتفية، أو البريد الإلكتروني. يمكن تمكين تسجيل الدخول بخطوتين على خزانة الويب bitwarden.com. هل تريد زيارة الموقع الآن؟" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "مقفل" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 27b94d34ee7..bdb3f89b422 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Yararsız ana parol" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "İki addımlı giriş, güvənlik açarı, kimlik doğrulayıcı tətbiq, SMS, telefon zəngi və ya e-poçt kimi digər cihazlarla girişinizi doğrulamanızı tələb edərək hesabınızı daha da güvənli edir. İki addımlı giriş, bitwarden.com veb seyfində qurula bilər. Veb saytı indi ziyarət etmək istəyirsiniz?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Kilidli" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 475ef8805f8..0664c25d84e 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Памылковы асноўны пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Двухэтапны ўваход робіць ваш уліковы запіс больш бяспечным, патрабуючы пацвярджэнне ўваходу на іншай прыладзе з выкарыстаннем ключа бяспекі, праграмы аўтэнтыфікацыі, SMS, тэлефоннага званка або электроннай пошты. Двухэтапны ўваход уключаецца на bitwarden.com. Перайсці на вэб-сайт, каб зрабіць гэта?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Заблакіравана" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index c40afa02073..bb0ad98f55a 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Грешна главна парола" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Двустепенното вписване защищава регистрацията ви като ви кара да потвърдите влизането си чрез устройство-ключ, приложение за идентификация, мобилно съобщение, телефонно обаждане или е-поща. Двустепенното вписване може да се включи чрез сайта bitwarden.com. Искате ли да го посетите?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Заключено" }, @@ -4115,11 +4142,11 @@ "message": "Редактиране на комбинацията" }, "archiveNoun": { - "message": "Archive", + "message": "Архив", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архивиране", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index c5dc6f5372f..da0ce08dca8 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "অবৈধ মূল পাসওয়ার্ড" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "দ্বি-পদক্ষেপ লগইন অন্য ডিভাইসে আপনার লগইনটি যাচাই করার জন্য সিকিউরিটি কী, প্রমাণীকরণকারী অ্যাপ্লিকেশন, এসএমএস, ফোন কল বা ই-মেইল ব্যাবহারের মাধ্যমে আপনার অ্যাকাউন্টকে আরও সুরক্ষিত করে। bitwarden.com ওয়েব ভল্টে দ্বি-পদক্ষেপের লগইন সক্ষম করা যাবে। আপনি কি এখনই ওয়েবসাইটটি দেখতে চান?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index b484970d59a..41260bf2b11 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Neispravna glavna lozinka" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Prijava u dva koraka čini Vaš račun sigurnijim tako što zahtjeva da verifikujete svoje podatke pomoću drugog uređaja, kao što su sigurnosni ključ, aplikacija za autentifikaciju, SMS, telefonski poziv ili E-Mail. Prijavljivanje u dva koraka može se omogućiti na bitwarden.com web trezoru. Da li želite da posjetite web stranicu sada?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index e3864cd7cc9..9bf578efb9f 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Contrasenya mestra no vàlida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "L'inici de sessió en dues passes fa que el vostre compte siga més segur, ja que obliga a comprovar el vostre inici de sessió amb un altre dispositiu, com ara una clau de seguretat, una aplicació autenticadora, un SMS, una trucada telefònica o un correu electrònic. Es pot habilitar l'inici de sessió en dues passes a la caixa forta web de bitwarden.com. Voleu visitar el lloc web ara?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Bloquejat" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index d0539502661..b056641ce64 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Chybné hlavní heslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Dvoufázové přihlášení činí Váš účet mnohem bezpečnějším díky nutnosti po každém úspěšném přihlášení zadat ověřovací kód získaný z bezpečnostního klíče, aplikace, SMS, telefonního hovoru nebo e-mailu. Dvoufázové přihlášení lze aktivovat na webové stránce bitwarden.com. Chcete tuto stránku nyní otevřít?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Bude exportován jen trezor organizace přidružený k $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Bude exportován jen trezor organizace přidružený k $ORGANIZATION$. Položky mých sbírek nebudou zahrnuty.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Uzamčeno" }, @@ -4115,11 +4142,11 @@ "message": "Upravit zkratku" }, "archiveNoun": { - "message": "Archive", + "message": "Archiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivovat", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 77063607b47..616673932ca 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index dcbd21d3fe9..9d46c37eea2 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Ugyldig hovedadgangskode" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Totrins-login gør kontoen mere sikker ved at kræve, at man bekræfter sit login med en anden enhed, såsom en sikkerhedsnøgle, godkendelses-app, SMS, telefonopkald eller e-mail. Totrins-login kan aktiveres via bitwarden.com web-boksen. Besøg webstedet nu?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Låst" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 094024f1a3f..eb3b713bb3e 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -415,7 +415,7 @@ "message": "Authenticator-Schlüssel" }, "autofillOptions": { - "message": "Auto-Ausfüllen-Optionen" + "message": "Optionen für automatisches Ausfüllen" }, "websiteUri": { "message": "Website (URI)" @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Ungültiges Master-Passwort" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Mit der Zwei-Faktor-Authentifizierung wird dein Konto zusätzlich abgesichert, da jede Anmeldung mit einem anderen Gerät wie einem Sicherheitsschlüssel, einer Authentifizierungs-App, einer SMS, einem Anruf oder einer E-Mail verifiziert werden muss. Die Zwei-Faktor-Authentifizierung kann im bitwarden.com Web-Tresor aktiviert werden. Möchtest du die Website jetzt öffnen?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Nur der mit $ORGANIZATION$ verknüpfte Organisations-Tresor wird exportiert.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Nur der mit $ORGANIZATION$ verknüpfte Organisations-Tresor wird exportiert. Meine Eintrags-Sammlungen werden nicht eingeschlossen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Gesperrt" }, @@ -4115,11 +4142,11 @@ "message": "Verknüpfung bearbeiten" }, "archiveNoun": { - "message": "Archive", + "message": "Archiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivieren", "description": "Verb" }, "unarchive": { @@ -4132,7 +4159,7 @@ "message": "Keine Einträge im Archiv" }, "noItemsInArchiveDesc": { - "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Ergebnissen der Suche sowie Autofill-Vorschlägen ausgeschlossen." + "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Auto-Ausfüllen-Vorschlägen ausgeschlossen." }, "itemSentToArchive": { "message": "Eintrag an das Archiv gesendet" @@ -4144,6 +4171,6 @@ "message": "Eintrag archivieren" }, "archiveItemConfirmDesc": { - "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen und Autofill-Vorschlägen ausgeschlossen. Sind Sie sicher, dass Sie diesen Eintrag archivieren möchten?" + "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen. Bist du sicher, dass du diesen Eintrag archivieren möchtest?" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 3a0c5938e4b..da30273ec93 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Μη έγκυρος κύριος κωδικός πρόσβασης" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Η σύνδεση δύο βημάτων καθιστά τον λογαριασμό σας πιο ασφαλή απαιτώντας από εσάς να επαληθεύσετε τη σύνδεσή σας με άλλη συσκευή, όπως ένα κλειδί ασφαλείας, μία εφαρμογή αυθεντικοποίησης, ένα SMS, μία τηλεφωνική κλήση, ή ένα μήνυμα ηλ. ταχυδρομείου. Η σύνδεση δύο βημάτων μπορεί να ρυθμιστεί στη διαδικτυακή κρύπτη bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Κλειδωμένο" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index ec95d939e0a..5d80bb6e37e 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 7e07cbad38f..723af507de2 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index fb9fe5cca9c..540503079fa 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Ŝlosita" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 1dfd580f333..0c61da97f10 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Contraseña maestra no válida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "La autenticación en dos pasos hace que tu cuenta sea mucho más segura, requiriendo que introduzcas un código de seguridad de una aplicación de autenticación cada vez que accedes. La autenticación en dos pasos puede ser habilitada en la caja fuerte web de bitwarden.com. ¿Quieres visitar ahora el sitio web?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Bloqueado" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index f4b5fdcc11a..a4a36241e9f 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Vale ülemparool" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Kaheastmeline kinnitamine aitab konto turvalisust tõsta. Lisaks paroolile pead kontole ligipääsemiseks kinnitama sisselogimise päringu SMS-ga, telefonikõnega, autentimise rakendusega või e-postiga. Kaheastmelist kinnitust saab sisse lülitada bitwarden.com veebihoidlas. Soovid seda kohe avada?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Lukustatud" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 173c3ece03e..55f02f7d7d2 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Pasahitz nagusi baliogabea" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Bi urratseko saio hasiera dela eta, zure kontua seguruagoa da, beste aplikazio/gailu batekin saioa hastea eskatzen baitizu; adibidez, segurtasun-gako, autentifikazio-aplikazio, SMS, telefono dei edo email bidez. Bi urratseko saio hasiera bitwarden.com webgunean aktibatu daiteke. Orain joan nahi duzu webgunera?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Blokeatuta" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 9624647be95..bb82558cc04 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "کلمه عبور اصلی نامعتبر است" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "ورود دو مرحله‌ای باعث می شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه احراز هویت، پیامک، تماس تلفنی و یا رایانامه، اعتبار خود را با ایمنی بیشتر اثبات کند. ورود دو مرحله‌ای می‌تواند در bitwarden.com راه‌اندازی شود. آیا می‌خواهید از سایت بازدید کنید؟" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "قفل شد" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 3e2cfbc2650..2d764ce69b3 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Virheellinen pääsalasana" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Kaksivaiheinen kirjautuminen parantaa tilisi suojausta vaatimalla kirjautumisen vahvistuksen salasanan lisäksi suojausavaimen, todennussovelluksen, tekstiviestin, puhelun tai sähköpostin avulla. Voit ottaa kaksivaiheisen kirjautumisen käyttöön bitwarden.com‑verkkoholvissa. Haluatko avata sen nyt?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Lukittu" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 1ec8754bf96..2ce70f049e1 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Imbalidong master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Ang two-step login ay nagpapagaan sa iyong account sa pamamagitan ng pag-verify sa iyong login sa isa pang device tulad ng security key, authenticator app, SMS, tawag sa telepono o email. Ang two-step login ay maaaring magawa sa bitwarden.com web vault. Gusto mo bang bisitahin ang website ngayon?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Naka-lock" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index fda65187812..6c1a081926b 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Mot de passe principal invalide" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "L'authentification à deux facteurs rend votre compte plus sûr en vous demandant de vérifier votre connexion avec un autre dispositif tel qu'une clé de sécurité, une application d'authentification, un SMS, un appel téléphonique ou un courriel. L'authentification à deux facteurs peut être configurée sur le coffre web de bitwarden.com. Voulez-vous visiter le site web maintenant ?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Verrouillé" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index f301c30de7f..9afc8066572 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 0b1e1b7ac9a..fc4c4854d65 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "סיסמה ראשית שגויה" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "אימות דו שלבי הופך את החשבון שלך למאובטח יותר בכך שתצטרך לאשר התחברות בעזרת מפתח אבטחה, תוכנת אימות, SMS, שיחת טלפון, או אימייל. ניתן להפעיל את \"אימות דו שלבי\" בכספת שבאתר bitwarden.com. האם ברצונך לפתוח את האתר כעת?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "נעול" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 28c73a7b45f..f62a31c2635 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index c3dc662949a..2f49ac1eb6d 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Neispravna glavna lozinka" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Prijava dvostrukom autentifikacijom čini tvoj račun još sigurnijim tako što će zahtijevati potvrdu prijave drugim uređajem kao što je sigurnosni ključ, autentifikatorska aplikacija, SMS, poziv ili e-pošta. Prijavu dvostrukom autentifikacijom možeš omogućiti na web trezoru. Želiš li sada posjetiti bitwarden.com?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Zaključano" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 7f3d4572ea3..ba1e9191063 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "A mesterjelszó érvénytelen." }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "A kétlépcsős bejelentkezés biztonságosabbá teszi a fiókot azzal, hogy meg kell erősíteni a bejelentkezést egy másik olyan eszközzel mint például biztonsági kulcs, hitelesítő alkalmazás, SMS, telefonhívás vagy email. A kétlépcsős bejelentkezést a bitwarden.com webes széfben lehet megváltoztatni. Szeretnénk felkeresni most a webhelyet?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Csak a $ORGANIZATION$ szervezetehez kapcsolódó szervezeti széf kerül exportálásra.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Csak a $ORGANIZATION$ szervezethez kapcsolódó szervezeti széf kerül exportálásra. A saját elem gyűjtemények nem lesznek benne.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Lezárva" }, @@ -4115,11 +4142,11 @@ "message": "Parancsikon szerkesztése" }, "archiveNoun": { - "message": "Archive", + "message": "Archívum", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archívum", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index acd61bf97ec..445f999ca6d 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Sandi utama tidak valid" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Info masuk dua langkah membuat akun Anda lebih aman dengan mengharuskan Anda memverifikasi info masuk Anda dengan peranti lain seperti kode keamanan, aplikasi autentikasi, SMS, panggilan telepon, atau email. Info masuk dua langkah dapat diaktifkan di brankas web bitwarden.com. Anda ingin mengunjungi situs web sekarang?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Terkunci" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 09466c05807..97a00443f4d 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Password principale errata" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "La verifica in due passaggi rende il tuo account più sicuro richiedendoti di verificare il tuo login usando un altro dispositivo come una chiave di sicurezza, app di autenticazione, SMS, telefonata, o email. Può essere abilitata nella cassaforte web su bitwarden.com. Vuoi visitare il sito?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Bloccato" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 3231d7fa77f..7c1cd89dd61 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "マスターパスワードが間違っています" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "2段階認証を使うと、ログイン時にセキュリティキーや認証アプリ、SMS、電話やメールでの認証を必要にすることでアカウントをさらに安全に出来ます。2段階認証は bitwarden.com ウェブ保管庫で有効化できます。ウェブサイトを開きますか?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "ロック中" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 4cfdea94e38..4095eccade0 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -262,7 +262,7 @@ "message": "Remember until vault is locked" }, "premiumRequired": { - "message": "Premium required" + "message": "საჭიროა პრემიუმი" }, "premiumRequiredDesc": { "message": "A Premium membership is required to use this feature." @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -1601,7 +1610,7 @@ "message": "ყველაფრის ჩვენება" }, "quitBitwarden": { - "message": "Quit Bitwarden" + "message": "გამოდით Bitwarden-იდან" }, "valueCopied": { "message": "$VALUE$ copied", @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "დაბლოკილია" }, @@ -3852,7 +3879,7 @@ "message": "Move" }, "newFolder": { - "message": "New folder" + "message": "ახალი საქაღალდე" }, "folderName": { "message": "Folder Name" diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index f301c30de7f..9afc8066572 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 608c427149a..54909553293 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "ಅಮಾನ್ಯ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "ಭದ್ರತಾ ಕೀ, ದೃಢೀಕರಣ ಅಪ್ಲಿಕೇಶನ್, ಎಸ್‌ಎಂಎಸ್, ಫೋನ್ ಕರೆ ಅಥವಾ ಇಮೇಲ್‌ನಂತಹ ಮತ್ತೊಂದು ಸಾಧನದೊಂದಿಗೆ ನಿಮ್ಮ ಲಾಗಿನ್ ಅನ್ನು ಪರಿಶೀಲಿಸುವ ಅಗತ್ಯವಿರುವ ಎರಡು ಹಂತದ ಲಾಗಿನ್ ನಿಮ್ಮ ಖಾತೆಯನ್ನು ಹೆಚ್ಚು ಸುರಕ್ಷಿತಗೊಳಿಸುತ್ತದೆ. ಬಿಟ್ವಾರ್ಡೆನ್.ಕಾಮ್ ವೆಬ್ ವಾಲ್ಟ್ನಲ್ಲಿ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು. ನೀವು ಈಗ ವೆಬ್‌ಸೈಟ್‌ಗೆ ಭೇಟಿ ನೀಡಲು ಬಯಸುವಿರಾ?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 8bbf4634a29..f210b5b5cae 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "잘못된 마스터 비밀번호" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "2단계 인증은 보안 키, 인증 앱, SMS, 전화 통화 등의 다른 기기로 사용자의 로그인 시도를 검증하여 사용자의 계정을 더욱 안전하게 만듭니다. 2단계 인증은 bitwarden.com 웹 보관함에서 활성화할 수 있습니다. 지금 웹 사이트를 방문하시겠습니까?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "잠김" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 152cdd03e86..e03c9dcd0f8 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Neteisingas pagrindinis slaptažodis" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Prisijungus dviem veiksmais, jūsų paskyra tampa saugesnė, reikalaujant patvirtinti prisijungimą naudojant kitą įrenginį, pvz., Saugos raktą, autentifikavimo programą, SMS, telefono skambutį ar el. Paštą. Dviejų žingsnių prisijungimą galima įjungti „bitwarden.com“ interneto saugykloje. Ar norite dabar apsilankyti svetainėje?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Užrakinta" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 2ccd255ea5b..d8a37b5d79c 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Nederīga galvenā parole" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Divpakāpju pieteikšanās padara kontu krietni drošāku, pieprasot apstiprināt pieteikšanos ar tādu citu ierīču vai pakalpojumu starpniecību kā drošības atslēga, autentificētāja lietotne, īsziņa, tālruņa zvans vai e-pasts. Divpakāpju pieteikšanos var iespējot bitwarden.com tīmekļa glabātavā. Vai tagad apmeklēt tīmekļvietni?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Aizslēgta" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 6509e5aafc2..6f6086ebc57 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Nevažeća glavna lozinka" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Prijavljivanje u dva koraka čini vaš nalog sigurnijim tako što ćete morati da verifikujete prijavu na drugom uređaju, kao što su bezbjedonosni ključ, aplikacija za potvrđivanje, SMS, telefonski poziv ili e-pošta. Prijava u dva koraka može se omogućiti u trezoru na internet strani bitwarden.com. Da li želite da posjetite internet lokaciju sada?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 03ed49a8fea..4bc82a9a543 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "അസാധുവായ പ്രാഥമിക പാസ്‌വേഡ്" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "സുരക്ഷാ കീ, ഓതന്റിക്കേറ്റർ അപ്ലിക്കേഷൻ, SMS, ഫോൺ കോൾ അല്ലെങ്കിൽ ഇമെയിൽ പോലുള്ള മറ്റൊരു ഉപകരണം ഉപയോഗിച്ച് തങ്ങളുടെ ലോഗിൻ സ്ഥിരീകരിക്കാൻ ആവശ്യപ്പെടുന്നതിലൂടെ രണ്ട്-ഘട്ട ലോഗിൻ തങ്ങളുടെ അക്കൗണ്ടിനെ കൂടുതൽ സുരക്ഷിതമാക്കുന്നു. bitwarden.com വെബ് വാൾട്ടിൽ രണ്ട്-ഘട്ട ലോഗിൻ പ്രവർത്തനക്ഷമമാക്കാനാകും.തങ്ങള്ക്കു ഇപ്പോൾ വെബ്സൈറ്റ് സന്ദർശിക്കാൻ ആഗ്രഹമുണ്ടോ?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index f301c30de7f..9afc8066572 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 58562cfc522..cb01bd340b5 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 97d3291875c..3e7d5a646eb 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Ugyldig hovedpassord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "2-trinnsinnlogging gjør kontoen din mer sikker, ved å kreve at du verifiserer din innlogging med en annen enhet, f.eks. en autentiseringsapp, SMS, E-post, telefonsamtale, eller sikkerhetsnøkkel. 2-trinnsinnlogging kan aktiveres på bitwarden.com-netthvelvet. Vil du besøke den nettsiden nå?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Låst" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 7c34e823553..12edd751bff 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 719be29bf91..36d6c661a78 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Ongeldig hoofdwachtwoord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Tweestapsaanmelding beschermt je account door je inlogpoging te bevestigen met een ander apparaat zoals een beveiligingssleutel, authenticatie-app, SMS, spraakoproep of e-mail. Je kunt Tweestapsaanmelding inschakelen in de webkluis op bitwarden.com. Wil je de website nu bezoeken?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Alleen de organisatiekluis die gekoppeld is aan $ORGANIZATION$ wordt geëxporteerd.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Exporteert alleen de organisatiekluis van $ORGANIZATION$. Geen persoonlijke kluis-items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Vergrendeld" }, @@ -4115,11 +4142,11 @@ "message": "Snelkoppeling bewerken" }, "archiveNoun": { - "message": "Archive", + "message": "Archief", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archiveren", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index de0a57b0a79..f33379eed7b 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Ugyldig hovudpassord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 2212adcf311..54971e9edc0 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index d364ac819d9..243cae0be72 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Hasło główne jest nieprawidłowe" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Logowanie dwustopniowe zwiększa bezpieczeństwo konta, wymagając weryfikacji logowania za pomocą innego urządzenia, takiego jak klucz bezpieczeństwa, aplikacja uwierzytelniająca, wiadomość SMS, połączenie telefoniczne lub wiadomość e-mail. Logowanie dwustopniowe możesz skonfigurować w sejfie internetowym bitwarden.com. Czy chcesz przejść do strony?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Zablokowane" }, @@ -4115,11 +4142,11 @@ "message": "Edytuj skrót" }, "archiveNoun": { - "message": "Archive", + "message": "Archiwum", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archiwizuj", "description": "Verb" }, "unarchive": { @@ -4144,6 +4171,6 @@ "message": "Archiwizuj element" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Zarchiwizowane elementy są wykluczone z wyników wyszukiwania i sugestii autouzupełniania. Czy na pewno chcesz archiwizować element?" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index dc28cf47345..0731e7f6bc8 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Senha mestra inválida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "A autenticação em duas etapas torna sua conta mais segura, exigindo que você verifique o seu login com outro dispositivo, como uma chave de segurança, um aplicativo de autenticação, SMS, chamada telefônica ou e-mail. A autenticação em duas etapas pode ser ativada no cofre web em bitwarden.com. Você deseja visitar o site agora?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Bloqueado" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 5ba1c52f7a6..71030592b4c 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Palavra-passe mestra inválida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "A verificação de dois passos torna a sua conta mais segura, exigindo que verifique o seu início de sessão com outro dispositivo, como uma chave de segurança, aplicação de autenticação, SMS, chamada telefónica ou e-mail. A verificação de dois passos pode ser configurada em bitwarden.com. Pretende visitar o site agora?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Bloqueado" }, @@ -4115,11 +4142,11 @@ "message": "Editar atalho" }, "archiveNoun": { - "message": "Archive", + "message": "Arquivo", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arquivar", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 624cc8ef2c1..5d72560fabb 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Parolă principală incorectă" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Autentificarea în două etape vă face contul mai sigur, cerându-vă să vă verificați autentificarea cu un alt dispozitiv, cum ar fi o cheie de securitate, o aplicație de autentificare, un SMS, un apel telefonic sau un e-mail. Autentificarea în două etape poate fi configurată pe seiful web bitwarden.com. Doriți să vizitați site-ul web acum?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Blocat" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 54262cd9ec9..f933557d9c9 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Неверный мастер-пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Двухэтапная аутентификация делает аккаунт более защищенным, поскольку требуется подтверждение входа при помощи другого устройства, например, ключа безопасности, приложения-аутентификатора, SMS, телефонного звонка или электронной почты. Двухэтапная аутентификация включается на bitwarden.com. Перейти на сайт сейчас?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$. Коллекции Мои элементы включены не будут.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Заблокировано" }, @@ -4115,11 +4142,11 @@ "message": "Изменить ярлык" }, "archiveNoun": { - "message": "Archive", + "message": "Архив", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архивировать", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 5b009f67f33..65cd68c9d53 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index a37ff270fd9..17713ac4655 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Neplatné hlavné heslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Dvojstupňové prihlasovanie robí váš účet bezpečnejším vďaka vyžadovaniu bezpečnostného kódu z overovacej aplikácie vždy, keď sa prihlásite. Dvojstupňové prihlasovanie môžete povoliť vo webovom trezore bitwarden.com. Chcete teraz navštíviť túto stránku?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Exportuje sa len trezor organizácie spojený s $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Exportuje sa len trezor organizácie spojený s $ORGANIZATION$. Moje zbierky položiek nebudú zahrnuté.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Zamknutý" }, @@ -4115,11 +4142,11 @@ "message": "Upraviť skratku" }, "archiveNoun": { - "message": "Archive", + "message": "Archív", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivovať", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 5141d046876..dc1585c2431 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Napačno glavno geslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Avtentikacija v dveh korakih naredi vaš račun bolj varen, saj od vas zahteva, da svojo prijavo preverite z drugo napravo, kot je varnostni ključ, aplikacija za preverjanje pristnosti, SMS, telefonski klic ali e-pošta. V spletnem trezorju bitwarden.com je lahko omogočite prijavo v dveh korakih. Ali želite spletno stran obiskati sedaj?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 5a4b0500183..87c52401aa9 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Погрешна главна лозинка" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Пријава у два корака чини ваш налог сигурнијим захтевом да верификујете своје податке помоћу другог уређаја, као што су безбедносни кључ, апликација, СМС-а, телефонски позив или имејл. Пријављивање у два корака може се омогућити на веб сефу. Да ли желите да посетите веб страницу сада?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Закључано" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 26e97befd42..a605e15badb 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Ogiltigt huvudlösenord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Tvåstegsverifiering gör ditt konto säkrare genom att kräva att du verifierar din inloggning med en annan enhet, t.ex. en säkerhetsnyckel, autentiseringsapp, SMS, telefonsamtal eller e-post. Tvåstegsverifiering kan aktiveras i Bitwardens webbvalv. Vill du besöka webbplatsen nu?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Endast organisationsvalvet som är associerat med $ORGANIZATION$ kommer att exporteras.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Endast organisationsvalvet som associeras med $ORGANIZATION$ kommer att exporteras. Mina objektsamlingar kommer inte att inkluderas.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Låst" }, @@ -4115,11 +4142,11 @@ "message": "Redigera genväg" }, "archiveNoun": { - "message": "Archive", + "message": "Arkiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arkivera", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index 9fd0d75f2d1..1fb449c891f 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "தவறான முதன்மை கடவுச்சொல்" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "இரண்டு-படி உள்நுழைவு உங்கள் கணக்கை ஒரு பாதுகாப்பு விசை, அங்கீகரிப்பான் செயலி, SMS, ஃபோன் அழைப்பு அல்லது மின்னஞ்சல் போன்ற மற்றொரு சாதனம் மூலம் உங்கள் உள்நுழைவை சரிபார்க்க கோருவதன் மூலம் அதை மேலும் பாதுகாக்கிறது. bitwarden.com இணைய பெட்டகத்தில் இரண்டு-படி உள்நுழைவை அமைக்கலாம். இப்போது இணையதளத்திற்குச் செல்ல விரும்புகிறீர்களா?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "பூட்டப்பட்டது" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index f301c30de7f..9afc8066572 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 28d1a6bffcd..1a66578c55c 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "รหัสผ่านหลักไม่ถูกต้อง" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "การเข้าสู่ระบบแบบสองขั้นตอนทำให้บัญชีของคุณมีความปลอดภัยมากขึ้นด้วยการให้คุณตรวจสอบการเข้าสู่ระบบของคุณกับอุปกรณ์อื่นเช่นคีย์ความปลอดภัย, แอพ authenticator, SMS, โทรศัพท์หรืออีเมล. เข้าสู่ระบบแบบสองขั้นตอนสามารถเปิดใช้งานบน เว็บนิรภัย bitwarden.com คุณต้องการเยี่ยมชมเว็บไซต์เดี๋ยวนี้หรือไม่" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Locked" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index dd47f2662a3..25ac25758c6 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Geçersiz ana parola" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "İki aşamalı giriş, hesabınıza girererken işlemi bir güvenlik anahtarı, şifrematik uygulaması, SMS, telefon araması veya e-posta gibi ek bir yöntemle doğrulamanızı isteyerek hesabınızın güvenliğini artırır. İki aşamalı giriş özelliğini bitwarden.com web kasası üzerinden ayarlayabilirsiniz. Şimdi siteye gitmek ister misiniz?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Kilitli" }, @@ -4115,11 +4142,11 @@ "message": "Kısayolu düzenle" }, "archiveNoun": { - "message": "Archive", + "message": "Arşiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arşivle", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 7f9e3a00fc3..3a38ad2666e 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Неправильний головний пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Двоетапна перевірка дає змогу надійніше захистити ваш обліковий запис, вимагаючи підтвердження входу з використанням іншого пристрою, наприклад, за допомогою ключа безпеки, програми автентифікації, SMS, телефонного виклику, або е-пошти. Ви можете налаштувати двоетапну перевірку в сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Заблоковано" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index e255e69d55c..d027c9d0f56 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "Mật khẩu chính không hợp lệ" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "Đăng nhập hai bước giúp 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 việc đăng nhập 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. Đăng nhập hai bước có thể được thiết lập trên bitwarden.com. Bạn có muốn truy cập trang web bây giờ không?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "Đã khóa" }, @@ -4084,13 +4111,13 @@ "message": "Bitwarden không kiểm tra vị trí nhập liệu, hãy đảm bảo bạn đang ở trong đúng cửa sổ và trường nhập liệu trước khi dùng phím tắt." }, "typeShortcut": { - "message": "Type shortcut" + "message": "Phím tắt nhập liệu" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "Bao gồm một hoặc hai trong số các phím bổ trợ sau: Ctrl, Alt, Win hoặc Shift, và một chữ cái." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "Phím tắt không hợp lệ" }, "moreBreadcrumbs": { "message": "Thêm mục điều hướng", @@ -4106,7 +4133,7 @@ "message": "Xác nhận" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "Bật phím tắt tự động nhập (Xem trước tính năng)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Hãy đảm bảo bạn đang ở đúng trường trước khi sử dụng phím tắt để tránh điền dữ liệu vào chỗ không đúng." @@ -4115,11 +4142,11 @@ "message": "Chỉnh sửa phím tắt" }, "archiveNoun": { - "message": "Archive", + "message": "Lưu trữ", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Lưu trữ", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index ed7f93f3768..8e2dfd67acd 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "无效的主密码" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "两步登录要求您从其他设备(例如安全密钥、验证器 App、短信、电话或者电子邮件)来验证您的登录,这能使您的账户更加安全。两步登录需要在 bitwarden.com 网页版密码库中设置。现在访问此网站吗?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "已锁定" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 9671957c9cc..60bf5cae265 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1222,6 +1222,15 @@ "invalidMasterPassword": { "message": "無效的主密碼" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "twoStepLoginConfirmation": { "message": "兩步驟登入需要您從其他裝置(例如安全鑰匙、驗證器程式、SMS、手機或電子郵件)來驗證您的登入,這使您的帳戶更加安全。兩步驟登入可以在 bitwarden.com 網頁版密碼庫啟用。現在要前往嗎?" }, @@ -2654,6 +2663,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "locked": { "message": "已鎖定" }, From 0443c878674577e0a75abb6fc92ec12096cbe161 Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Fri, 3 Oct 2025 08:58:07 -0500 Subject: [PATCH 47/83] [PM-26018] All Activity tab - Password change metric (#16644) --- apps/web/src/locales/en/messages.json | 50 ++++- .../services/all-activities.service.ts | 29 +++ .../reports/risk-insights/services/index.ts | 1 + .../security-tasks-api.service.spec.ts | 53 +++++ .../services/security-tasks-api.service.ts | 25 +++ .../access-intelligence.module.ts | 6 + .../activity-card.component.ts | 2 +- .../password-change-metric.component.html | 75 +++++++ .../password-change-metric.component.ts | 204 ++++++++++++++++++ .../all-activity.component.html | 18 +- .../all-activity.component.ts | 15 +- .../critical-applications.component.ts | 1 + 12 files changed, 469 insertions(+), 10 deletions(-) create mode 100644 bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.spec.ts create mode 100644 bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.ts create mode 100644 bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html create mode 100644 bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 9b26ec271c0..939e703b040 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, - "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, + "criticalApplicationsActivityDescription": { + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -9523,6 +9566,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts index f1eebf81d73..3ea67d8f7c9 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts @@ -1,5 +1,6 @@ import { BehaviorSubject } from "rxjs"; +import { LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher } from "../models"; import { OrganizationReportSummary } from "../models/report-models"; export class AllActivitiesService { @@ -22,6 +23,18 @@ export class AllActivitiesService { reportSummary$ = this.reportSummarySubject$.asObservable(); + private allApplicationsDetailsSubject$: BehaviorSubject< + LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[] + > = new BehaviorSubject<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[]>([]); + allApplicationsDetails$ = this.allApplicationsDetailsSubject$.asObservable(); + + private atRiskPasswordsCountSubject$ = new BehaviorSubject<number>(0); + atRiskPasswordsCount$ = this.atRiskPasswordsCountSubject$.asObservable(); + + private passwordChangeProgressMetricHasProgressBarSubject$ = new BehaviorSubject<boolean>(false); + passwordChangeProgressMetricHasProgressBar$ = + this.passwordChangeProgressMetricHasProgressBarSubject$.asObservable(); + setCriticalAppsReportSummary(summary: OrganizationReportSummary) { this.reportSummarySubject$.next({ ...this.reportSummarySubject$.getValue(), @@ -41,4 +54,20 @@ export class AllActivitiesService { totalAtRiskApplicationCount: summary.totalAtRiskApplicationCount, }); } + + setAllAppsReportDetails( + applications: LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[], + ) { + const totalAtRiskPasswords = applications.reduce( + (sum, app) => sum + app.atRiskPasswordCount, + 0, + ); + this.atRiskPasswordsCountSubject$.next(totalAtRiskPasswords); + + this.allApplicationsDetailsSubject$.next(applications); + } + + setPasswordChangeProgressMetricHasProgressBar(hasProgressBar: boolean) { + this.passwordChangeProgressMetricHasProgressBarSubject$.next(hasProgressBar); + } } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts index 69d936d3016..53ee3ffa892 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/index.ts @@ -6,3 +6,4 @@ export * from "./risk-insights-api.service"; export * from "./risk-insights-report.service"; export * from "./risk-insights-data.service"; export * from "./all-activities.service"; +export * from "./security-tasks-api.service"; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.spec.ts new file mode 100644 index 00000000000..63fec460162 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.spec.ts @@ -0,0 +1,53 @@ +import { mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { SecurityTasksApiService, TaskMetrics } from "./security-tasks-api.service"; + +describe("SecurityTasksApiService", () => { + const apiServiceMock = mock<ApiService>(); + let service: SecurityTasksApiService; + + beforeEach(() => { + service = new SecurityTasksApiService(apiServiceMock); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + describe("getTaskMetrics", () => { + it("should call apiService.send with correct parameters", (done) => { + const orgId = { toString: () => "org-123" } as OrganizationId; + const mockMetrics: TaskMetrics = { completedTasks: 2, totalTasks: 5 }; + apiServiceMock.send.mockReturnValue(Promise.resolve(mockMetrics)); + + service.getTaskMetrics(orgId).subscribe((metrics) => { + expect(apiServiceMock.send).toHaveBeenCalledWith( + "GET", + "/tasks/org-123/metrics", + null, + true, + true, + ); + expect(metrics).toEqual(mockMetrics); + done(); + }); + }); + + it("should propagate errors from apiService.send", (done) => { + const orgId = { toString: () => "org-456" } as OrganizationId; + const error = new Error("API error"); + apiServiceMock.send.mockReturnValue(Promise.reject(error)); + + service.getTaskMetrics(orgId).subscribe({ + next: () => {}, + error: (err: unknown) => { + expect(err).toBe(error); + done(); + }, + }); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.ts new file mode 100644 index 00000000000..92bb9207453 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/security-tasks-api.service.ts @@ -0,0 +1,25 @@ +import { from, Observable } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +export type TaskMetrics = { + completedTasks: number; + totalTasks: number; +}; + +export class SecurityTasksApiService { + constructor(private apiService: ApiService) {} + + getTaskMetrics(orgId: OrganizationId): Observable<TaskMetrics> { + const dbResponse = this.apiService.send( + "GET", + `/tasks/${orgId.toString()}/metrics`, + null, + true, + true, + ); + + return from(dbResponse as Promise<TaskMetrics>); + } +} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts index 6848220446b..01bf19f30a4 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts @@ -10,6 +10,7 @@ import { RiskInsightsApiService, RiskInsightsDataService, RiskInsightsReportService, + SecurityTasksApiService, } from "@bitwarden/bit-common/dirt/reports/risk-insights/services"; import { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -79,6 +80,11 @@ import { RiskInsightsComponent } from "./risk-insights.component"; useClass: AllActivitiesService, deps: [], }), + safeProvider({ + provide: SecurityTasksApiService, + useClass: SecurityTasksApiService, + deps: [ApiService], + }), ], }) export class AccessIntelligenceModule {} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts index 7de339358f3..2dc7c6a9c79 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts @@ -11,7 +11,7 @@ import { ButtonModule, LinkModule, TypographyModule } from "@bitwarden/component imports: [CommonModule, TypographyModule, JslibModule, LinkModule, ButtonModule], host: { class: - "tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-border [&:not(bit-layout_*)]:tw-rounded-lg tw-rounded-lg tw-p-6", + "tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-border [&:not(bit-layout_*)]:tw-rounded-lg tw-rounded-lg tw-p-6 tw-h-56 tw-max-h-56", }, }) export class ActivityCardComponent { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html new file mode 100644 index 00000000000..9b194954f0e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.html @@ -0,0 +1,75 @@ +<div + class="tw-flex tw-flex-col tw-p-6 tw-box-border tw-bg-background tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-rounded-lg tw-h-56 tw-max-h-56" +> + <div bitTypography="h6" class="tw-mb-2"> + {{ "passwordChangeProgress" | i18n }} + </div> + + @if (renderMode === renderModes.noCriticalApps) { + <div class="tw-items-start tw-mb-2"> + <span bitTypography="h3">{{ "assignMembersTasksToMonitorProgress" | i18n }}</span> + </div> + + <div class="tw-items-baseline tw-gap-2"> + <span bitTypography="body2">{{ "onceYouReviewApplications" | i18n }}</span> + </div> + } + + @if (renderMode === renderModes.criticalAppsWithAtRiskAppsAndNoTasks) { + <div class="tw-items-start tw-mb-2"> + <span bitTypography="h3">{{ "assignMembersTasksToMonitorProgress" | i18n }}</span> + </div> + + <div class="tw-items-baseline tw-gap-2"> + <span bitTypography="body2">{{ "countOfAtRiskPasswords" | i18n: atRiskPasswordsCount }}</span> + </div> + + <div class="tw-mt-4"> + <button + bitButton + buttonType="secondary" + type="button" + [disabled]="!canAssignTasks" + (click)="assignTasks()" + > + <i class="bwi bwi-envelope tw-mr-2"></i> + {{ "assignTasks" | i18n }} + </button> + </div> + } + + @if (renderMode === renderModes.criticalAppsWithAtRiskAppsAndTasks) { + <div class="tw-items-start tw-mb-2"> + <span bitTypography="h3">{{ "percentageCompleted" | i18n: completedPercent }}</span> + </div> + + <div class="tw-items-baseline tw-gap-2"> + <span bitTypography="body2">{{ + "securityTasksCompleted" | i18n: completedTasksCount : totalTasksCount + }}</span> + </div> + + <div class="tw-mt-4"> + <div class="tw-flex tw-justify-between"> + <div bitTypography="body2">{{ completedTasksCount }}</div> + <div bitTypography="body2">{{ totalTasksCount }}</div> + </div> + </div> + <bit-progress + [showText]="false" + size="small" + bgColor="primary" + [barWidth]="completedPercent" + [ariaLabel]="'passwordChangeProgressBar' | i18n" + > + </bit-progress> + + <!-- TODO: Implement reminder functionality --> + <!-- <div class="tw-items-start tw-mt-4 tw-gap-4"> + <button bitButton type="button" buttonType="secondary"> + <i class="bwi bwi-envelope" aria-hidden="true"></i> + {{ "sendReminders" | i18n }} + </button> + </div> --> + } +</div> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts new file mode 100644 index 00000000000..b7a36a79988 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-cards/password-change-metric.component.ts @@ -0,0 +1,204 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { Subject, switchMap, takeUntil, of, BehaviorSubject, combineLatest } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + AllActivitiesService, + LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher, + SecurityTasksApiService, + TaskMetrics, +} from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTaskType } from "@bitwarden/common/vault/tasks"; +import { + ButtonModule, + ProgressModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; + +import { CreateTasksRequest } from "../../../vault/services/abstractions/admin-task.abstraction"; +import { DefaultAdminTaskService } from "../../../vault/services/default-admin-task.service"; + +export const RenderMode = { + noCriticalApps: "noCriticalApps", + criticalAppsWithAtRiskAppsAndNoTasks: "criticalAppsWithAtRiskAppsAndNoTasks", + criticalAppsWithAtRiskAppsAndTasks: "criticalAppsWithAtRiskAppsAndTasks", +} as const; +export type RenderMode = (typeof RenderMode)[keyof typeof RenderMode]; + +@Component({ + selector: "dirt-password-change-metric", + imports: [CommonModule, TypographyModule, JslibModule, ProgressModule, ButtonModule], + templateUrl: "./password-change-metric.component.html", + providers: [DefaultAdminTaskService], +}) +export class PasswordChangeMetricComponent implements OnInit { + protected taskMetrics$ = new BehaviorSubject<TaskMetrics>({ totalTasks: 0, completedTasks: 0 }); + private completedTasks: number = 0; + private totalTasks: number = 0; + private allApplicationsDetails: LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[] = + []; + + atRiskAppsCount: number = 0; + atRiskPasswordsCount: number = 0; + private organizationId!: OrganizationId; + private destroyRef = new Subject<void>(); + renderMode: RenderMode = "noCriticalApps"; + + async ngOnInit(): Promise<void> { + this.activatedRoute.paramMap + .pipe( + switchMap((paramMap) => { + const orgId = paramMap.get("organizationId"); + if (orgId) { + this.organizationId = orgId as OrganizationId; + return this.securityTasksApiService.getTaskMetrics(this.organizationId); + } + return of({ totalTasks: 0, completedTasks: 0 }); + }), + takeUntil(this.destroyRef), + ) + .subscribe((metrics) => { + this.taskMetrics$.next(metrics); + }); + + combineLatest([ + this.taskMetrics$, + this.allActivitiesService.reportSummary$, + this.allActivitiesService.atRiskPasswordsCount$, + this.allActivitiesService.allApplicationsDetails$, + ]) + .pipe(takeUntil(this.destroyRef)) + .subscribe(([taskMetrics, summary, atRiskPasswordsCount, allApplicationsDetails]) => { + this.atRiskAppsCount = summary.totalCriticalAtRiskApplicationCount; + this.atRiskPasswordsCount = atRiskPasswordsCount; + this.completedTasks = taskMetrics.completedTasks; + this.totalTasks = taskMetrics.totalTasks; + this.allApplicationsDetails = allApplicationsDetails; + + // No critical apps setup + this.renderMode = + summary.totalCriticalApplicationCount === 0 ? RenderMode.noCriticalApps : this.renderMode; + + // Critical apps setup with at-risk apps but no tasks + this.renderMode = + summary.totalCriticalApplicationCount > 0 && + summary.totalCriticalAtRiskApplicationCount >= 0 && + taskMetrics.totalTasks === 0 + ? RenderMode.criticalAppsWithAtRiskAppsAndNoTasks + : this.renderMode; + + // Critical apps setup with at-risk apps and tasks + this.renderMode = + summary.totalAtRiskApplicationCount > 0 && + summary.totalCriticalAtRiskApplicationCount >= 0 && + taskMetrics.totalTasks > 0 + ? RenderMode.criticalAppsWithAtRiskAppsAndTasks + : this.renderMode; + + this.allActivitiesService.setPasswordChangeProgressMetricHasProgressBar( + this.renderMode === RenderMode.criticalAppsWithAtRiskAppsAndTasks, + ); + }); + } + + constructor( + private activatedRoute: ActivatedRoute, + private securityTasksApiService: SecurityTasksApiService, + private allActivitiesService: AllActivitiesService, + private adminTaskService: DefaultAdminTaskService, + protected toastService: ToastService, + protected i18nService: I18nService, + ) {} + + get completedPercent(): number { + if (this.totalTasks === 0) { + return 0; + } + return Math.round((this.completedTasks / this.totalTasks) * 100); + } + + get completedTasksCount(): number { + switch (this.renderMode) { + case RenderMode.noCriticalApps: + case RenderMode.criticalAppsWithAtRiskAppsAndNoTasks: + return 0; + + case RenderMode.criticalAppsWithAtRiskAppsAndTasks: + return this.completedTasks; + + default: + return 0; + } + } + + get totalTasksCount(): number { + switch (this.renderMode) { + case RenderMode.noCriticalApps: + return 0; + + case RenderMode.criticalAppsWithAtRiskAppsAndNoTasks: + return this.atRiskAppsCount; + + case RenderMode.criticalAppsWithAtRiskAppsAndTasks: + return this.totalTasks; + + default: + return 0; + } + } + + get canAssignTasks(): boolean { + return this.atRiskAppsCount > this.totalTasks ? true : false; + } + + get renderModes() { + return RenderMode; + } + + async assignTasks() { + const taskCount = await this.requestPasswordChange(); + this.taskMetrics$.next({ + totalTasks: this.totalTasks + taskCount, + completedTasks: this.completedTasks, + }); + } + + // TODO: this method is shared between here and critical-applications.component.ts + async requestPasswordChange() { + const apps = this.allApplicationsDetails; + const cipherIds = apps + .filter((_) => _.atRiskPasswordCount > 0) + .flatMap((app) => app.atRiskCipherIds); + + const distinctCipherIds = Array.from(new Set(cipherIds)); + + const tasks: CreateTasksRequest[] = distinctCipherIds.map((cipherId) => ({ + cipherId: cipherId as CipherId, + type: SecurityTaskType.UpdateAtRiskCredential, + })); + + try { + await this.adminTaskService.bulkCreateTasks(this.organizationId as OrganizationId, tasks); + this.toastService.showToast({ + message: this.i18nService.t("notifiedMembers"), + variant: "success", + title: this.i18nService.t("success"), + }); + + return tasks.length; + } catch { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + variant: "error", + title: this.i18nService.t("error"), + }); + } + + return 0; + } +} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index 8d564502ee4..3e60347fcef 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -17,10 +17,15 @@ } @if (!(isLoading$ | async) && !(noData$ | async)) { - <div class="tw-mt-4 tw-flex tw-flex-col"> - <div class="tw-flex tw-gap-4 tw-col-span-6"> + <ul + class="tw-inline-grid tw-grid-cols-3 tw-gap-6 tw-m-0 tw-p-0 tw-w-full tw-auto-cols-auto tw-list-none" + > + <li class="tw-col-span-1" [ngClass]="{ 'tw-col-span-2': passwordChangeMetricHasProgressBar }"> + <dirt-password-change-metric></dirt-password-change-metric> + </li> + + <li class="tw-col-span-1"> <dirt-activity-card - class="tw-col-span-2 tw-cursor-pointer" [title]="'atRiskMembers' | i18n" [cardMetrics]="'membersAtRiskCount' | i18n: totalCriticalAppsAtRiskMemberCount" [metricDescription]="'membersAtRiskActivityDescription' | i18n" @@ -29,10 +34,11 @@ [showNavigationLink]="totalCriticalAppsAtRiskMemberCount > 0" > </dirt-activity-card> + </li> + <li class="tw-col-span-1"> <dirt-activity-card #allAppsOrgAtRiskApplications - class="tw-col-span-2 tw-cursor-pointer" [title]="'criticalApplications' | i18n" [cardMetrics]=" totalCriticalAppsCount === 0 @@ -50,6 +56,6 @@ [showNavigationLink]="totalCriticalAppsAtRiskCount > 0" > </dirt-activity-card> - </div> - </div> + </li> + </ul> } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts index e69dc2b06e5..f1aa6f1041b 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts @@ -15,12 +15,18 @@ import { getById } from "@bitwarden/common/platform/misc"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { ActivityCardComponent } from "./activity-card.component"; +import { PasswordChangeMetricComponent } from "./activity-cards/password-change-metric.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; import { RiskInsightsTabType } from "./risk-insights.component"; @Component({ selector: "tools-all-activity", - imports: [ApplicationsLoadingComponent, SharedModule, ActivityCardComponent], + imports: [ + ApplicationsLoadingComponent, + SharedModule, + ActivityCardComponent, + PasswordChangeMetricComponent, + ], templateUrl: "./all-activity.component.html", }) export class AllActivityComponent implements OnInit { @@ -30,6 +36,7 @@ export class AllActivityComponent implements OnInit { totalCriticalAppsAtRiskMemberCount = 0; totalCriticalAppsCount = 0; totalCriticalAppsAtRiskCount = 0; + passwordChangeMetricHasProgressBar = false; destroyRef = inject(DestroyRef); @@ -51,6 +58,12 @@ export class AllActivityComponent implements OnInit { this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; }); + + this.allActivitiesService.passwordChangeProgressMetricHasProgressBar$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((hasProgressBar) => { + this.passwordChangeMetricHasProgressBar = hasProgressBar; + }); } } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts index f092b1575f0..7848d37ea94 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts @@ -101,6 +101,7 @@ export class CriticalApplicationsComponent implements OnInit { this.applicationSummary = this.reportService.generateApplicationsSummary(applications); this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0; this.allActivitiesService.setCriticalAppsReportSummary(this.applicationSummary); + this.allActivitiesService.setAllAppsReportDetails(applications); } }); } From 33dc57890a9fbd0923e6b3c94549b8816e8908b5 Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:04:39 -0500 Subject: [PATCH 48/83] Add premium guard to phishing detection service (#16602) --- .../browser/src/background/main.background.ts | 6 +- .../phishing-detection.service.spec.ts | 88 +++++++++++++++++-- .../services/phishing-detection.service.ts | 44 +++++++--- 3 files changed, 120 insertions(+), 18 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 7b62178d237..fef0181352c 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1415,12 +1415,14 @@ export default class MainBackground { this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); PhishingDetectionService.initialize( - this.configService, + this.accountService, this.auditService, + this.billingAccountProfileStateService, + this.configService, + this.eventCollectionService, this.logService, this.storageService, this.taskSchedulerService, - this.eventCollectionService, ); this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService); diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts index 8d3c3ec5b31..d6aca6abea0 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts @@ -2,6 +2,8 @@ import { of } from "rxjs"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -10,35 +12,109 @@ import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling/task import { PhishingDetectionService } from "./phishing-detection.service"; describe("PhishingDetectionService", () => { + let accountService: AccountService; let auditService: AuditService; + let billingAccountProfileStateService: BillingAccountProfileStateService; + let configService: ConfigService; + let eventCollectionService: EventCollectionService; let logService: LogService; let storageService: AbstractStorageService; let taskSchedulerService: TaskSchedulerService; - let configService: ConfigService; - let eventCollectionService: EventCollectionService; beforeEach(() => { + accountService = { getAccount$: jest.fn(() => of(null)) } as any; auditService = { getKnownPhishingDomains: jest.fn() } as any; + billingAccountProfileStateService = {} as any; + configService = { getFeatureFlag$: jest.fn(() => of(false)) } as any; + eventCollectionService = {} as any; logService = { info: jest.fn(), debug: jest.fn(), warning: jest.fn(), error: jest.fn() } as any; storageService = { get: jest.fn(), save: jest.fn() } as any; taskSchedulerService = { registerTaskHandler: jest.fn(), setInterval: jest.fn() } as any; - configService = { getFeatureFlag$: jest.fn(() => of(false)) } as any; - eventCollectionService = {} as any; }); it("should initialize without errors", () => { expect(() => { PhishingDetectionService.initialize( - configService, + accountService, auditService, + billingAccountProfileStateService, + configService, + eventCollectionService, logService, storageService, taskSchedulerService, - eventCollectionService, ); }).not.toThrow(); }); + it("should enable phishing detection for premium account", (done) => { + const premiumAccount = { id: "user1" }; + accountService = { activeAccount$: of(premiumAccount) } as any; + configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any; + billingAccountProfileStateService = { + hasPremiumFromAnySource$: jest.fn(() => of(true)), + } as any; + + // Patch _setup to call done + const setupSpy = jest + .spyOn(PhishingDetectionService as any, "_setup") + .mockImplementation(async () => { + expect(setupSpy).toHaveBeenCalled(); + done(); + }); + + // Run the initialization + PhishingDetectionService.initialize( + accountService, + auditService, + billingAccountProfileStateService, + configService, + eventCollectionService, + logService, + storageService, + taskSchedulerService, + ); + }); + + it("should not enable phishing detection for non-premium account", (done) => { + const nonPremiumAccount = { id: "user2" }; + accountService = { activeAccount$: of(nonPremiumAccount) } as any; + configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any; + billingAccountProfileStateService = { + hasPremiumFromAnySource$: jest.fn(() => of(false)), + } as any; + + // Patch _setup to fail if called + // [FIXME] This test needs to check if the setupSpy fails or is called + // Refactor initialize in PhishingDetectionService to return a Promise or Observable that resolves/completes when initialization is done + // So that spy setups can be properly verified after initialization + // const setupSpy = jest + // .spyOn(PhishingDetectionService as any, "_setup") + // .mockImplementation(async () => { + // throw new Error("Should not call _setup"); + // }); + + // Patch _cleanup to call done + const cleanupSpy = jest + .spyOn(PhishingDetectionService as any, "_cleanup") + .mockImplementation(() => { + expect(cleanupSpy).toHaveBeenCalled(); + done(); + }); + + // Run the initialization + PhishingDetectionService.initialize( + accountService, + auditService, + billingAccountProfileStateService, + configService, + eventCollectionService, + logService, + storageService, + taskSchedulerService, + ); + }); + it("should detect phishing domains", () => { PhishingDetectionService["_knownPhishingDomains"].add("phishing.com"); const url = new URL("https://phishing.com"); diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts index 1497ac96dba..54245ae17b4 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts @@ -1,7 +1,18 @@ -import { concatMap, delay, Subject, Subscription } from "rxjs"; +import { + combineLatest, + concatMap, + delay, + EMPTY, + map, + Subject, + Subscription, + switchMap, +} from "rxjs"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -41,31 +52,44 @@ export class PhishingDetectionService { private static _lastUpdateTime: number = 0; static initialize( - configService: ConfigService, + accountService: AccountService, auditService: AuditService, + billingAccountProfileStateService: BillingAccountProfileStateService, + configService: ConfigService, + eventCollectionService: EventCollectionService, logService: LogService, storageService: AbstractStorageService, taskSchedulerService: TaskSchedulerService, - eventCollectionService: EventCollectionService, ): void { this._auditService = auditService; this._logService = logService; this._storageService = storageService; this._taskSchedulerService = taskSchedulerService; - logService.info("[PhishingDetectionService] Initialize called"); + logService.info("[PhishingDetectionService] Initialize called. Checking prerequisites..."); - configService - .getFeatureFlag$(FeatureFlag.PhishingDetection) + combineLatest([ + accountService.activeAccount$, + configService.getFeatureFlag$(FeatureFlag.PhishingDetection), + ]) .pipe( - concatMap(async (enabled) => { - if (!enabled) { + switchMap(([account, featureEnabled]) => { + if (!account) { + logService.info("[PhishingDetectionService] No active account."); + this._cleanup(); + return EMPTY; + } + return billingAccountProfileStateService + .hasPremiumFromAnySource$(account.id) + .pipe(map((hasPremium) => ({ hasPremium, featureEnabled }))); + }), + concatMap(async ({ hasPremium, featureEnabled }) => { + if (!hasPremium || !featureEnabled) { logService.info( - "[PhishingDetectionService] Phishing detection feature flag is disabled.", + "[PhishingDetectionService] User does not have access to phishing detection service.", ); this._cleanup(); } else { - // Enable phishing detection service logService.info("[PhishingDetectionService] Enabling phishing detection service"); await this._setup(); } From f2cde04014608740b60d71b9981bb3e454b2a337 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:12:06 +0000 Subject: [PATCH 49/83] Autosync the updated translations (#16716) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 33 +++++++ apps/web/src/locales/ar/messages.json | 33 +++++++ apps/web/src/locales/az/messages.json | 33 +++++++ apps/web/src/locales/be/messages.json | 33 +++++++ apps/web/src/locales/bg/messages.json | 37 +++++++- apps/web/src/locales/bn/messages.json | 33 +++++++ apps/web/src/locales/bs/messages.json | 33 +++++++ apps/web/src/locales/ca/messages.json | 33 +++++++ apps/web/src/locales/cs/messages.json | 37 +++++++- apps/web/src/locales/cy/messages.json | 33 +++++++ apps/web/src/locales/da/messages.json | 33 +++++++ apps/web/src/locales/de/messages.json | 111 +++++++++++++++-------- apps/web/src/locales/el/messages.json | 33 +++++++ apps/web/src/locales/en_GB/messages.json | 33 +++++++ apps/web/src/locales/en_IN/messages.json | 33 +++++++ apps/web/src/locales/eo/messages.json | 33 +++++++ apps/web/src/locales/es/messages.json | 33 +++++++ apps/web/src/locales/et/messages.json | 33 +++++++ apps/web/src/locales/eu/messages.json | 33 +++++++ apps/web/src/locales/fa/messages.json | 33 +++++++ apps/web/src/locales/fi/messages.json | 33 +++++++ apps/web/src/locales/fil/messages.json | 33 +++++++ apps/web/src/locales/fr/messages.json | 37 +++++++- apps/web/src/locales/gl/messages.json | 33 +++++++ apps/web/src/locales/he/messages.json | 33 +++++++ apps/web/src/locales/hi/messages.json | 33 +++++++ apps/web/src/locales/hr/messages.json | 33 +++++++ apps/web/src/locales/hu/messages.json | 37 +++++++- apps/web/src/locales/id/messages.json | 33 +++++++ apps/web/src/locales/it/messages.json | 33 +++++++ apps/web/src/locales/ja/messages.json | 33 +++++++ apps/web/src/locales/ka/messages.json | 33 +++++++ apps/web/src/locales/km/messages.json | 33 +++++++ apps/web/src/locales/kn/messages.json | 33 +++++++ apps/web/src/locales/ko/messages.json | 33 +++++++ apps/web/src/locales/lv/messages.json | 33 +++++++ apps/web/src/locales/ml/messages.json | 33 +++++++ apps/web/src/locales/mr/messages.json | 33 +++++++ apps/web/src/locales/my/messages.json | 33 +++++++ apps/web/src/locales/nb/messages.json | 33 +++++++ apps/web/src/locales/ne/messages.json | 33 +++++++ apps/web/src/locales/nl/messages.json | 37 +++++++- apps/web/src/locales/nn/messages.json | 33 +++++++ apps/web/src/locales/or/messages.json | 33 +++++++ apps/web/src/locales/pl/messages.json | 35 ++++++- apps/web/src/locales/pt_BR/messages.json | 33 +++++++ apps/web/src/locales/pt_PT/messages.json | 37 +++++++- apps/web/src/locales/ro/messages.json | 33 +++++++ apps/web/src/locales/ru/messages.json | 37 +++++++- apps/web/src/locales/si/messages.json | 33 +++++++ apps/web/src/locales/sk/messages.json | 33 +++++++ apps/web/src/locales/sl/messages.json | 33 +++++++ apps/web/src/locales/sr_CS/messages.json | 33 +++++++ apps/web/src/locales/sr_CY/messages.json | 33 +++++++ apps/web/src/locales/sv/messages.json | 53 +++++++++-- apps/web/src/locales/ta/messages.json | 33 +++++++ apps/web/src/locales/te/messages.json | 33 +++++++ apps/web/src/locales/th/messages.json | 33 +++++++ apps/web/src/locales/tr/messages.json | 51 +++++++++-- apps/web/src/locales/uk/messages.json | 33 +++++++ apps/web/src/locales/vi/messages.json | 33 +++++++ apps/web/src/locales/zh_CN/messages.json | 33 +++++++ apps/web/src/locales/zh_TW/messages.json | 33 +++++++ 63 files changed, 2152 insertions(+), 73 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 5690392f205..0cfad02f7a1 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Ongeldige hoofwagwoord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Toegang geweiger. U het nie toestemming om hierdie blad te sien nie." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 98b84791237..b9c581f98e2 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "كلمة المرور الرئيسية غير صالحة" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "كلمة مرور الملف غير صالحة، الرجاء استخدام كلمة المرور التي أدخلتها عند إنشاء ملف التصدير." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 7b9bbe01c62..716fdcb4ea9 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Yararsız ana parol" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Yararsız fayl parolu, lütfən xaricə köçürmə faylını yaradarkən daxil etdiyiniz parolu istifadə edin." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Erişim rədd edildi. Bu səhifəyə baxmaq üçün icazəniz yoxdur." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden uzantısı quraşdırıldı!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Giriş etmək üçün uzantını aç və avto-doldurmağa başla." }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index e1f5639cda4..1a4ca699212 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Памылковы асноўны пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Памылковы пароль файла. Скарыстайцеся паролем, які вы ўводзілі пры стварэнні файла экспартавання." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Доступ забаронены. У вас не дастаткова правоў для прагляду гэтай старонкі." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 19119a31c34..a7d6043e253 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Грешна главна парола" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Неправилна парола за файла. Използвайте паролата, която сте въвели при създаването на изнесения файл." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$. Моите колекции няма да бъдат включени.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Достъпът е отказан. Нямате право за преглед на тази страница." }, @@ -11075,11 +11102,11 @@ "message": "Търсене в архива" }, "archiveNoun": { - "message": "Archive", + "message": "Архив", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архивиране", "description": "Verb" }, "noItemsInArchive": { @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Добавката на Битуорден е инсталирана!" }, + "openTheBitwardenExtension": { + "message": "Отварете добавката на Битуорден" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "Добавката на Битуорден е инсталирана! Отворете я, за да се впишете и да можете да се вписвате автоматично." + }, "openExtensionToAutofill": { "message": "Отворете добавката, за да се впишете и да можете да използвате автоматичното попълване." }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index e5d2671bb9e..62dda4f7dd1 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "অবৈধ মূল পাসওয়ার্ড" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 26835283fc2..65033f7d1d7 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 8dc3825e8c5..d1b5888dbef 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Contrasenya mestra no vàlida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "La contrasenya del fitxer no és vàlida. Utilitzeu la contrasenya que vau introduir quan vau crear el fitxer d'exportació." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Accés denegat. No teniu permís per veure aquesta pàgina." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 2fd97aab27e..b0e84d27a4c 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Chybné hlavní heslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Neplatné heslo souboru, použijte heslo zadané při vytvoření souboru exportu." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Bude exportován jen trezor organizace přidružený k $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Bude exportován jen trezor organizace přidružený k $ORGANIZATION$. Položky mých sbírek nebudou zahrnuty.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Přístup byl odepřen. Nemáte oprávnění k zobrazení této stránky." }, @@ -11075,11 +11102,11 @@ "message": "Hledat v archivu" }, "archiveNoun": { - "message": "Archive", + "message": "Archiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivovat", "description": "Verb" }, "noItemsInArchive": { @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Rozšíření Bitwarden nainstalováno!" }, + "openTheBitwardenExtension": { + "message": "Otevřít rozšíření Bitwarden" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "Rozšíření Bitwarden je nainstalováno! Otevřete rozšíření pro přihlášení a začněte používat automatické vyplňování." + }, "openExtensionToAutofill": { "message": "Otevřete rozšíření pro příhlášení a spuštění automatického vyplňování." }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 5fba67468aa..905707d7393 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 00e6f8c2389..2e175920a6d 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Ugyldig hovedadgangskode" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Ugyldig filadgangskode. Brug samme adgangskode, som da du oprettede eksportfilen." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Adgang nægtet. Nødvendig tilladelse til at se siden mangler." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 8019ed6950b..eeac818b2f8 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -232,10 +232,10 @@ "message": "Anwendungen insgesamt" }, "unmarkAsCritical": { - "message": "Als kritisch-Markierung aufheben" + "message": "Markierung als kritisch aufheben" }, "criticalApplicationUnmarkedSuccessfully": { - "message": "Anwendung erfolgreich als kritisch-Markierung aufgehoben" + "message": "Markierung der Anwendung als kritisch erfolgreich aufgehoben" }, "whatTypeOfItem": { "message": "Um welche Art von Eintrag handelt es sich hierbei?" @@ -810,11 +810,11 @@ "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "Neuen Text senden", + "message": "Neues Text-Send", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "Neue Datei senden", + "message": "Neues Datei-Send", "description": "Header for new file send" }, "editItemHeaderLogin": { @@ -838,11 +838,11 @@ "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Text bearbeiten Send", + "message": "Text-Send bearbeiten", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Datei bearbeiten Send", + "message": "Datei-Send bearbeiten", "description": "Header for edit file send" }, "viewItemHeaderLogin": { @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Ungültiges Master-Passwort" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Ungültiges Dateipasswort. Bitte verwende das Passwort, das du beim Erstellen der Exportdatei eingegeben hast." }, @@ -1518,7 +1527,7 @@ "message": "Keine Einträge im Tresor" }, "emptyVaultDescription": { - "message": "Der Tresor schützt mehr als nur Ihre Passwörter. Speichern Sie hier sichere Zugangsdaten, Ausweise, Karten und Notizen." + "message": "Der Tresor schützt mehr als nur deine Passwörter. Speicher hier sichere Zugangsdaten, Identitäten, Karten und Notizen." }, "emptyFavorites": { "message": "Du hast keine Einträge zu Favoriten hinzugefügt" @@ -4825,10 +4834,10 @@ "message": "Organisation ist deaktiviert" }, "organizationIsSuspended": { - "message": "Organisation ist gesperrt" + "message": "Organisation ist deaktiviert" }, "organizationIsSuspendedDesc": { - "message": "Auf Einträge in gesperrten Organisationen kann nicht zugegriffen werden. Wenden Sie sich an den Eigentümer Ihrer Organisation, um Unterstützung zu erhalten." + "message": "Auf Einträge in deaktivierten Organisationen kann nicht zugegriffen werden. Kontaktiere den Eigentümer deiner Organisation, um Unterstützung zu erhalten." }, "secretsAccessSuspended": { "message": "Auf deaktivierte Organisationen kann nicht zugegriffen werden. Bitte wende dich an deinen Organisationseigentümer." @@ -5216,11 +5225,11 @@ "message": "SSO-Kennung" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "message": "Gib deinen Mitgliedern diese ID für die Anmeldung mit SSO. Mitglieder können die Eingabe dieser Kennung während des SSO überspringen, wenn eine beanspruchte Domain eingerichtet ist. ", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "Mehr erfahren", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -5620,7 +5629,7 @@ "message": "Desktop-Autotype-Standard­einstellung" }, "desktopAutotypePolicyDesc": { - "message": "Aktivieren Sie Desktop-Autotype standardmäßig für Mitglieder. Mitglieder können Autotype im Desktop-Client manuell deaktivieren.", + "message": "Aktiviere Desktop-Autotype standardmäßig für Mitglieder. Mitglieder können Autotype im Desktop-Client manuell deaktivieren.", "description": "This policy will enable Desktop Autotype by default for members on Unlock." }, "disableSend": { @@ -6788,7 +6797,7 @@ "message": "SSO deaktiviert" }, "emailMustLoginWithSso": { - "message": "$EMAIL$ muss sich mit Single Sign-on anmelden", + "message": "$EMAIL$ muss sich mit Single Sign-On anmelden", "placeholders": { "email": { "content": "$1", @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Nur der mit $ORGANIZATION$ verknüpfte Organisations-Tresor wird exportiert.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Nur der mit $ORGANIZATION$ verknüpfte Organisations-Tresor wird exportiert. Meine Eintrags-Sammlungen werden nicht eingeschlossen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Zugriff verweigert. Du hast keine Berechtigung, diese Seite zu sehen." }, @@ -7189,7 +7216,7 @@ "message": "Unbekanntes Geheimnis, möglicherweise müssen Sie eine Berechtigung anfordern, um auf dieses Geheimnis zuzugreifen." }, "unknownProject": { - "message": "Unbekanntes Projekt, möglicherweise müssen Sie eine Berechtigung anfordern, um auf dieses Projekt zuzugreifen." + "message": "Unbekanntes Projekt. Du musst möglicherweise eine Berechtigung anfordern, um auf dieses Projekt zuzugreifen." }, "cannotSponsorSelf": { "message": "Du kannst nicht für das aktive Konto einlösen. Gib eine andere E-Mail ein." @@ -9081,19 +9108,19 @@ "message": "Verwaltung der Sammlungen" }, "collectionManagementDescription": { - "message": "Konfigurieren Sie das Sammlungsverhalten für die Organisation" + "message": "Konfiguriere das Sammlungs-Verhalten für die Organisation" }, "allowAdminAccessToAllCollectionItemsDescription": { - "message": "Erlauben Sie Eigentümern und Administratoren, alle Sammlungen und Einträge über die Admin-Konsole zu verwalten" + "message": "Erlaube Eigentümern und Administratoren, alle Sammlungen und Einträge über die Admin-Konsole zu verwalten" }, "restrictCollectionCreationDescription": { - "message": "Beschränken Sie die Erstellung von Sammlungen auf Eigentümer und Administratoren" + "message": "Beschränke die Erstellung von Sammlungen auf Eigentümer und Administratoren" }, "restrictCollectionDeletionDescription": { - "message": "Beschränken Sie das Löschen von Sammlungen auf Eigentümer und Administratoren" + "message": "Beschränke das Löschen von Sammlungen auf Eigentümer und Administratoren" }, "restrictItemDeletionDescriptionStart": { - "message": "Beschränken Sie das Löschen von Einträgen auf Mitglieder mit der ", + "message": "Beschränke das Löschen von Einträgen auf Mitglieder mit der ", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "restrictItemDeletionDescriptionEnd": { @@ -9177,7 +9204,7 @@ } }, "limitCollectionCreationEnabled": { - "message": "Die Einstellung Sammlungserstellung einschränken wurde für $ID$ aktiviert.", + "message": "Die Einstellung Sammlungs-Erstellung einschränken wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9186,7 +9213,7 @@ } }, "limitCollectionCreationDisabled": { - "message": "Die Einstellung Sammlungserstellung einschränken wurde für $ID$ deaktiviert.", + "message": "Die Einstellung Sammlungs-Erstellung einschränken wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9195,7 +9222,7 @@ } }, "limitCollectionDeletionEnabled": { - "message": "Die Einstellung Sammlungslöschung einschränken wurde für $ID$ aktiviert.", + "message": "Die Einstellung Sammlungs-Löschung einschränken wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9204,7 +9231,7 @@ } }, "limitCollectionDeletionDisabled": { - "message": "Die Einstellung Sammlungslöschung einschränken wurde für $ID$ deaktiviert.", + "message": "Die Einstellung Sammlungs-Löschung einschränken wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9213,7 +9240,7 @@ } }, "limitItemDeletionEnabled": { - "message": "Die Einstellung Eintragslöschung einschränken wurde für $ID$ aktiviert.", + "message": "Die Einstellung Eintrags-Löschung einschränken wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9222,7 +9249,7 @@ } }, "limitItemDeletionDisabled": { - "message": "Die Einstellung Eintragslöschung einschränken wurde für $ID$ deaktiviert.", + "message": "Die Einstellung Eintrags-Löschung einschränken wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9809,10 +9836,10 @@ "message": "Fehler beim Speichern der Integration. Bitte versuche es später erneut." }, "mustBeOrgOwnerToPerformAction": { - "message": "Sie müssen Eigentümer der Organisation sein, um diese Aktion auszuführen." + "message": "Du musst der Eigentümer der Organisation sein, um diese Aktion auszuführen." }, "failedToDeleteIntegration": { - "message": "Löschen der Integration fehlgeschlagen. Bitte versuchen Sie es später erneut." + "message": "Löschen der Integration fehlgeschlagen. Bitte versuche es später erneut." }, "deviceIdMissing": { "message": "Geräte-ID fehlt" @@ -11045,7 +11072,7 @@ "message": "Gefährdetes Passwort ändern" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "Diese Zugangsdaten sind gefährdet und enthalten keine Website. Fügen Sie eine Website hinzu und ändern Sie das Passwort für mehr Sicherheit." + "message": "Diese Zugangsdaten sind gefährdet und es fehlt eine Website. Füge eine Website hinzu und ändere das Passwort für mehr Sicherheit." }, "missingWebsite": { "message": "Fehlende Webseite" @@ -11075,18 +11102,18 @@ "message": "Archiv durchsuchen" }, "archiveNoun": { - "message": "Archive", + "message": "Archiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivieren", "description": "Verb" }, "noItemsInArchive": { "message": "Keine Einträge im Archiv" }, "archivedItemsDescription": { - "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Autofill-Vorschlägen ausgeschlossen." + "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Auto-Ausfüllen-Vorschlägen ausgeschlossen." }, "businessUnit": { "message": "Geschäftsbereich" @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden-Erweiterung installiert!" }, + "openTheBitwardenExtension": { + "message": "Die Bitwarden-Erweiterung öffnen" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "Die Bitwarden-Erweiterung ist installiert! Öffne die Erweiterung, um dich anzumelden und das automatische Ausfüllen zu starten." + }, "openExtensionToAutofill": { "message": "Öffne die Erweiterung, um dich anzumelden und Auto-Ausfüllen zu nutzen." }, @@ -11246,7 +11279,7 @@ "message": "Über diese Einstellung" }, "permitCipherDetailsDescription": { - "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu bestimmen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um Ihr Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn Sie diesen Dienst nutzen." + "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu bestimmen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um dein Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn du diesen Dienst nutzt." }, "billingAddress": { "message": "Rechnungsadresse" @@ -11413,10 +11446,10 @@ "message": "Key Connector-Domain bestätigen" }, "requiredToVerifyBankAccountWithStripe": { - "message": "Zahlungen mit einem Bankkonto sind nur für Kunden in den Vereinigten Staaten verfügbar. Sie müssen Ihr Bankkonto verifizieren. Wir werden innerhalb der nächsten 1–2 Werktage eine kleine Einzahlung vornehmen. Wenn das Bankkonto nicht verifiziert wird, führt dies zu einer verpassten Zahlung und die Abonnement wird ausgesetzt." + "message": "Zahlungen mit einem Bankkonto sind nur für Kunden in den Vereinigten Staaten verfügbar. Du musst dein Bankkonto verifizieren. Wir werden innerhalb der nächsten 1–2 Werktage eine geringfügige Einzahlung vornehmen. Wenn das Bankkonto nicht verifiziert wird, führt dies zu einer verpassten Zahlung und dein Abonnement wird ausgesetzt." }, "verifyBankAccountWithStripe": { - "message": "Wir haben eine kleine Einzahlung auf Ihr Bankkonto vorgenommen. Dies kann 1–2 Werktage dauern. Sobald Sie die Einzahlung auf Ihrem Konto sehen, können Sie Ihr Bankkonto verifizieren. Wenn das Bankkonto nicht verifiziert wird, führt dies zu einer verpassten Zahlung und Ihr Abonnement wird ausgesetzt." + "message": "Wir haben eine geringfügige Einzahlung auf dein Bankkonto vorgenommen. Dies kann 1–2 Werktage dauern. Sobald du die Einzahlung auf deinem Konto siehst, kannst du dein Bankkonto verifizieren. Wenn das Bankkonto nicht verifiziert wird, führt dies zu einer verpassten Zahlung und dein Abonnement wird ausgesetzt." }, "verifyNow": { "message": "Jetzt verifizieren." @@ -11446,10 +11479,10 @@ "message": "Umfassende Online-Sicherheit" }, "planDescFamiliesV2": { - "message": "Premium-Sicherheit für Ihre Familie" + "message": "Premium-Sicherheit für deine Familie" }, "planDescFreeV2": { - "message": "Mit $COUNT$ anderem Benutzer teilen", + "message": "Mit $COUNT$ anderen Benutzer teilen", "placeholders": { "count": { "content": "$1", @@ -11464,7 +11497,7 @@ "message": "Individueller Plan" }, "planDescCustom": { - "message": "Bitwarden wächst mit Unternehmen jeder Größe mit, um Passwörter und vertrauliche Informationen zu sichern. Wenn Sie Teil eines großen Unternehmens sind, kontaktieren Sie den Vertrieb, um ein Angebot anzufordern." + "message": "Bitwarden wächst mit Unternehmen jeder Größe mit, um Passwörter und vertrauliche Informationen zu sichern. Wenn du Teil eines großen Unternehmens bist, kontaktiere den Vertrieb, um ein Angebot anzufordern." }, "builtInAuthenticator": { "message": "Integrierter Authenticator" @@ -11479,7 +11512,7 @@ "message": "Sicherer Dateispeicher" }, "familiesUnlimitedSharing": { - "message": "Unbegrenztes Teilen – wählen Sie, wer was sieht" + "message": "Unbegrenztes Teilen – wähle, wer was sieht" }, "familiesUnlimitedCollections": { "message": "Unbegrenzte Familiensammlungen" @@ -11521,7 +11554,7 @@ } }, "secureItemSharing": { - "message": "Sicheres Eintrags-Teilen" + "message": "Sicheres Teilen von Einträgen" }, "scimSupport": { "message": "SCIM-Unterstützung" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index c0f2a338557..5c84285054e 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Μη έγκυρος κύριος κωδικός πρόσβασης" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Μη έγκυρος κωδικός πρόσβασης αρχείου, χρησιμοποιήστε τον κωδικό πρόσβασης που καταχωρήσατε όταν δημιουργήσατε το αρχείο εξαγωγής." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index f187d6484d3..a75d0369c1e 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index b69c7f4488c..b032523c548 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 8d51d1f4ded..2fa2257ef4f 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Nevalida majstra pasvorto" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Nevalida pasvorto de la dosiero, bonvolu uzi la pasvorton, kiun vi enmetis kiam vi kreis la elportan dosieron." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 0cedc53a0ab..f5e2629db6f 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Contraseña maestra no válida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Contraseña de archivo no válida, por favor utilice la contraseña que introdujo cuando creó el archivo de exportación." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Acceso denegado. No tiene permiso para ver esta página." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index dfc120b75fd..e540073d369 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Vale ülemparool" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Vale parool, palun kasuta seda parooli mille sisestasid eksport faili loomisel." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Ligipääs keelatud. Sul pole lubatud seda lehekülge vaadata." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index cc955354537..098cc94ef65 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Master pasahitz baliogabea" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Fitxategi pasahitzak ez du balio; mesedez, erabili esportazio fitxategia sortzean sartutako pasahitza." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Sarbidea ukatuta. Ez duzu baimenik orri hau ikusteko." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 973bfb3b413..4bdd8ecadc2 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "کلمه عبور اصلی نامعتبر است" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "کلمه عبور پرونده نامعتبر است، لطفاً از کلمه عبوری که هنگام ایجاد پرونده‌ی برون ریزی وارد کردید استفاده کنید." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "دسترسی رد شد. شما اجازه مشاهده این صفحه را ندارید." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "افزونه Bitwarden نصب شد!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "افزونه را باز کنید تا وارد شوید و پر کردن خودکار را آغاز کنید." }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index f717ca1e5a5..cfb9af1ffd2 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Virheellinen pääsalasana" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Tiedoston salasana on virheellinen. Käytä vientitiedoston luonnin yhteydessä syötettyä salasanaa." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Pääsy estetty. Sinulla ei ole oikeutta avata sivua." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 3e11a4eb943..37f90dcdfe2 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Hindi wasto ang master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Hindi wasto ang password ng file, mangyaring gamitin ang password na inilagay mo noong ginawa mo ang export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Hindi natanggap ang access. Wala kang pahintulot na tingnan ang pahinang ito." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index d1d00d7da63..73b9757694d 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Mot de passe principal invalide" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "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." }, @@ -5216,11 +5225,11 @@ "message": "Identifiant SSO" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "message": "Fournir cet ID à vos membres pour se connecter avec SSO. Les membres peuvent passer la saisie de cet identifiant pendant SSO si un domaine revendiqué est configuré. ", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "En savoir plus", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Seul le coffre de l'organisation associé à $ORGANIZATION$ sera exporté.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Seul le coffre de l'organisation associé à $ORGANIZATION$ sera exporté. Mes éléments de mes collections ne seront pas inclus.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Accès Refusé. Vous n'avez pas l'autorisation d'afficher cette page." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "L'extension Bitwarden est installée !" }, + "openTheBitwardenExtension": { + "message": "Ouvrir l'extension Bitwarden" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "L'extension Bitwarden est installée! Ouvrez l'extension pour vous connecter et démarrer le remplissage automatique." + }, "openExtensionToAutofill": { "message": "Ouvrez l'extension pour vous connecter et démarrer le remplissage automatique." }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 78589d8a3b6..b211482fe29 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 99a172a71d1..af4548a202d 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "סיסמה ראשית שגויה" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "סיסמת קובץ שגויה, נא להשתמש בסיסמה שהזנת כשיצרת את קובץ הייצוא." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "גישה נדחתה. אין לך הרשאות כדי לצפות בעמוד זה." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "הרחבת Bitwarden הותקנה!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "פתח את ההרחבה כדי להיכנס ולהתחיל למלא אוטומטית." }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 96fdeacc6ff..c9a1372b54f 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 6ca05ee3f62..4fdae01443c 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Neispravna glavna lozinka" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Nesipravna lozinka datoteke. Unesi lozinku izvozne datoteke." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Pristup odbijen. Nemaš prava vijdeti ovu stranicu." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden proširenje je instalirano!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Otvori proširenje i prijavi se za početak korištenja auto-ispune." }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index ba533d302f5..c4b6fcf08c1 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "A mesterjelszó érvénytelen." }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Érvénytelen a fájl jelszó. Használjuk az exportálás fájl létrehozásakor megadott jelszót." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Csak a $ORGANIZATION$ szervezetehez kapcsolódó szervezeti széf kerül exportálásra.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Csak a $ORGANIZATION$ szervezethez kapcsolódó szervezeti széf kerül exportálásra. A saját elem gyűjtemények nem lesznek benne.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "A hozzáférés megtagadásra került. Nincs jogosultság az oldal megtekintésére." }, @@ -11075,11 +11102,11 @@ "message": "Keresés archívum" }, "archiveNoun": { - "message": "Archive", + "message": "Archívum", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archívum", "description": "Verb" }, "noItemsInArchive": { @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "A Bitwarden bővítmény megnyitása" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "A Bitwarden bővítmény telepítve van! Nyissuk meg a bővítményt a bejelentkezéshez és az automatikus kitöltés megkezdéséhez." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 2de26459ff0..508f9b853d4 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Sandi utama tidak valid" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Kata sandi berkas tidak valid, harap menggunakan kata sandi yang anda masukkan saat anda membuat berkas ekspor." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Akses ditolak. Anda tidak mempunyai izin untuk melihat halaman ini." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index daeab7df8a1..68479fee66f 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Password principale errata" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Password errata, usa la password che hai inserito alla creazione del file di esportazione." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Accesso negato. Non hai i permessi necessari per visualizzare questa pagina." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Estensione di Bitwarden installata!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Apri l'estensione cliccando sul tasto della barra degli strumenti e accedi con i tuoi dati per attivare il riempimento automatico." }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index bf65ef30a28..ef7e01f927b 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "マスターパスワードが間違っています" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "無効なファイルパスワードです。エクスポートファイルを作成したときに入力したパスワードを使用してください。" }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "アクセスが拒否されました。このページを表示する権限がありません。" }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index ad1e820c179..c5f62bb779a 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "არასწორი მთავარი პაროლი" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "არასწორი ფაილის პაროლი, გთხოვთ გამოიყენოთ პაროლი რომელიც თქვენ შეიყვანეთ როცა შექმენით ექსპორტ ფაილი." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index fa4c124416d..70a88883ec4 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 08dfc457843..50b28bb0836 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "ಅಮಾನ್ಯ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 53af3e5313f..10894c6e795 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "잘못된 마스터 비밀번호" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "파일 암호가 잘못되었습니다. 내보낸 파일을 생성했을 때 사용한 암호를 입력하세요." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 7b6d22e3679..9cbac624833 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Nederīga galvenā parole" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izgūšanas datnes izveidošanas brīdī." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Piekļuve liegta. Nav nepieciešamo atļauju, lai apskatītu šo lapu." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden paplašinājums uzstādīts." }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Jāatver paplašinājums, lai pieteiktos un uzsāktu automātisko aizpildi." }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 2c1fde02227..da058b2ab25 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "അസാധുവായ പ്രാഥമിക പാസ്‌വേഡ്" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index c1aef5bc376..db36040f830 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index fa4c124416d..70a88883ec4 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index bf44b5f7397..c3a70fa4609 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Ugyldig hovedpassord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Ugyldig filpassord, bruk passordet du skrev inn da du opprettet eksportfilen." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Ingen tilgang. Du har ikke tillatelse til å se denne siden." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 29d4bd0da20..15bd69be119 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index f8db72ffe8c..6b6777d460c 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Ongeldig hoofdwachtwoord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Onjuist bestandswachtwoord, gebruik het wachtwoord dat je hebt ingevoerd bij het aanmaken van het exportbestand." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Alleen de organisatiekluis die gekoppeld is aan $ORGANIZATION$ wordt geëxporteerd.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Exporteert alleen de organisatiekluis van $ORGANIZATION$. Geen persoonlijke kluis-items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Toegang geweigerd. Je hebt geen toestemming om deze pagina te bekijken." }, @@ -11075,11 +11102,11 @@ "message": "Search archive" }, "archiveNoun": { - "message": "Archive", + "message": "Archief", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archiveren", "description": "Verb" }, "noItemsInArchive": { @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extensie geïnstalleerd!" }, + "openTheBitwardenExtension": { + "message": "De Bitwarden-extensie openen" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "De Bitwarden-extensie is geïnstalleerd! Open de extensie om in te loggen en te beginnen met automatisch invullen." + }, "openExtensionToAutofill": { "message": "Open de extensie om in te loggen en te beginnen met automatisch invullen." }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index ec6da6ee754..d30b23e35db 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Ugild hovudpassord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index fa4c124416d..70a88883ec4 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 20a8331b61e..43dc88f05b8 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Hasło główne jest nieprawidłowe" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Hasło pliku jest nieprawidłowe. Użyj prawidłowego hasła." }, @@ -5220,7 +5229,7 @@ "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "Dowiedz się więcej", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Odmowa dostępu. Nie masz uprawnień do przeglądania tej strony." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Rozszerzenie Bitwarden zainstalowane!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Otwórz rozszerzenie, aby zalogować się i rozpocząć autouzupełnianie." }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index c7e782c32eb..0a1a4223df3 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Senha mestra inválida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Senha do arquivo inválida, por favor informe a senha utilizada quando criou o arquivo de exportação." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Acesso negado. Você não tem permissão para ver esta página." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index ff2b97eb1d7..9bb89f881ac 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Palavra-passe mestra inválida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Palavra-passe de ficheiro inválida, utilize a palavra-passe que introduziu quando criou o ficheiro de exportação." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Acesso negado. Não tem permissão para ver esta página." }, @@ -11075,11 +11102,11 @@ "message": "Procurar no arquivo" }, "archiveNoun": { - "message": "Archive", + "message": "Arquivo", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arquivar", "description": "Verb" }, "noItemsInArchive": { @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Extensão Bitwarden instalada!" }, + "openTheBitwardenExtension": { + "message": "Abrir a extensão Bitwarden" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "A extensão Bitwarden está instalada! Abra a extensão para iniciar sessão e começar o preenchimento automático." + }, "openExtensionToAutofill": { "message": "Abra a extensão para iniciar sessão e começar o preenchimento automático." }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 974250b3934..8aaeaf7d87c 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Parolă principală incorectă" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Parola fișierului nu este validă, utilizați parola introdusă atunci când ați creat fișierul de export." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Acces refuzat. Nu aveți permisiunea de a vizualiza această pagină." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index f06fa1449b8..9808fae953e 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Неверный мастер-пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Неверный пароль к файлу. Используйте пароль, введенный при создании файла экспорта." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$. Коллекции Мои элементы включены не будут.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Доступ запрещен. У вас нет разрешения на просмотр этой страницы." }, @@ -11075,11 +11102,11 @@ "message": "Поиск в архиве" }, "archiveNoun": { - "message": "Archive", + "message": "Архив", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архивировать", "description": "Verb" }, "noItemsInArchive": { @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Расширение Bitwarden установлено!" }, + "openTheBitwardenExtension": { + "message": "Открыть расширение Bitwarden" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "Расширение Bitwarden установлено! Откройте расширение, чтобы авторизоваться и начать автозаполнение." + }, "openExtensionToAutofill": { "message": "Откройте расширение, чтобы авторизоваться и начать использовать автозаполнение." }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 062ea291fa1..dab19f43acd 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 8bafa670e29..32c26b6f9ca 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Neplatné hlavné heslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Neplatné heslo súboru, použite heslo, ktoré ste zadali pri vytváraní exportného súboru." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Prístup zamietnutý. Nemáte oprávnenie na zobrazenie tejto stránky." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Rozšírenie Bitwarden nainštalované!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Otvorte rozšírenie, prihláste sa a začnite automatické vypĺňanie." }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 17431aedecb..944faf782cf 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Napačno glavno geslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Napačno geslo za datoteko. Prosimo, vnesite geslo, ki ste ga vnesli, ko ste izvozili datoteko." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index c3278674a56..a0c93cc0ebb 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index b500c6230e4..8a17ca2dd9e 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Погрешна главна лозинка" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Неважећа лозинка за датотеку, користите лозинку коју сте унели када сте креирали датотеку за извоз." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Одбијен приступ. Немате дозволу да видите ову страницу." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden екстензија инсталираана!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Отворите екстензију да бисте се пријавили и започели ауто-попуњавање." }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 130d076eee6..5fa95a53fd0 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Ogiltigt huvudlösenord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Ogiltigt fillösenord, använd lösenordet du angav när du skapade exportfilen." }, @@ -1509,28 +1518,28 @@ "message": "Det finns inga objekt att visa." }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Inga objekt i papperskorgen" }, "noItemsInTrashDesc": { "message": "Items you delete will appear here and be permanently deleted after 30 days" }, "noItemsInVault": { - "message": "No items in the vault" + "message": "Inga objekt i valvet" }, "emptyVaultDescription": { "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." }, "emptyFavorites": { - "message": "You haven't favorited any items" + "message": "Du har inga favoritobjekt" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "Lägg till ofta använda objekt till favoriter för snabb åtkomst." }, "noSearchResults": { - "message": "No search results returned" + "message": "Inga sökresultat returnerades" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Töm filter eller försök med en annan sökterm" }, "noPermissionToViewAllCollectionItems": { "message": "Du har inte behörighet att se alla objekt i denna samling." @@ -4825,7 +4834,7 @@ "message": "Organisationen är inaktiverad" }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "Organisationen är avstängd" }, "organizationIsSuspendedDesc": { "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." @@ -5220,7 +5229,7 @@ "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "Läs mer", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Åtkomst nekad. Du har inte behörighet att visa den här sidan." }, @@ -11075,11 +11102,11 @@ "message": "Sök i arkiv" }, "archiveNoun": { - "message": "Archive", + "message": "Arkiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arkivera", "description": "Verb" }, "noItemsInArchive": { @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitvärdesförlängningen installerad!" }, + "openTheBitwardenExtension": { + "message": "Öppna Bitwarden-tillägget" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Öppna tillägget för att logga in och starta autofyllning." }, diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 3534de7e547..9552457b42a 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "தவறான முதன்மை கடவுச்சொல்" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "தவறான கோப்பு கடவுச்சொல், ஏற்றுமதி கோப்பை உருவாக்கியபோது நீங்கள் உள்ளிட்ட கடவுச்சொல்லைப் பயன்படுத்தவும்." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "அணுகல் மறுக்கப்பட்டது. இந்தப் பக்கத்தைப் பார்க்க உங்களுக்கு அனுமதி இல்லை." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden நீட்டிப்பு நிறுவப்பட்டது!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "உள்நுழைந்து தானாக நிரப்பத் தொடங்க நீட்டிப்பைத் திறக்கவும்." }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index fa4c124416d..70a88883ec4 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 717c78cb3ca..a55527f33a2 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "รหัสผ่านหลักไม่ถูกต้อง" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections 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." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 5a52f872f57..38fdd506966 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -60,7 +60,7 @@ "message": "Yeni hesap kaydı oluştur" }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Kritik olarak işaretlediğiniz uygulamalar burada görünecektir" }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -166,7 +166,7 @@ "message": "Kritik uygulamalar için risk altındaki kayıtlara düzenleme erişimi olan üyeler" }, "membersAtRiskCount": { - "message": "$COUNT$ members at-risk", + "message": "$COUNT$ üye risk altında", "placeholders": { "count": { "content": "$1", @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Geçersiz ana parola" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Geçersiz dosya parolası. Lütfen dışa aktardığınız dosyayı oluştururken girdiğiniz parolayı kullanın." }, @@ -1509,16 +1518,16 @@ "message": "Listelenecek kayıt yok." }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Çöp kutusunda hiç kayıt yok" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "Sildiğiniz kayıtlar burada görünecek ve 30 gün sonra kalıcı olarak silinecektir" }, "noItemsInVault": { - "message": "No items in the vault" + "message": "Kasada hiç kayıt yok" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "Kasanız sadece parolalarınız için değil. Hesaplarınızı, kimliklerinizi, kredi kartlarınızı ve notlarınızı da güvenle burada depolayabilirsiniz." }, "emptyFavorites": { "message": "You haven't favorited any items" @@ -1530,7 +1539,7 @@ "message": "No search results returned" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Filtreleri temizleyin veya başka bir arama yapmayı deneyin" }, "noPermissionToViewAllCollectionItems": { "message": "Bu koleksiyondaki tüm kayıtları görmek için izniniz yok." @@ -4825,10 +4834,10 @@ "message": "Kuruluş askıya alındı" }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "Kuruluş askıya alındı" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "Askıya alınmış kuruluşlardaki kayıtlara erişilemez. Destek almak için kuruluş sahibinizle iletişime geçin." }, "secretsAccessSuspended": { "message": "Askıya alınan kuruluşlara erişilemez. Lütfen yardım için kuruluşunuzun sahibiyle iletişime geçin." @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Erişim engellendi. Bu sayfayı görüntüleme iznine sahip değilsiniz." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden uzantısı yüklendi!" }, + "openTheBitwardenExtension": { + "message": "Bitwarden uzantısını aç" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Otomatik doldurmaya başlamak için uzantıyı açıp giriş yapın." }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index e1aa1169f43..90cc5c77c61 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Неправильний головний пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Неправильний пароль файлу. Використайте пароль, який ви вводили під час створення експортованого файлу." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Доступ заборонено. У вас немає дозволу на перегляд цієї сторінки." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Розширення Bitwarden встановлено!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Відкрийте розширення, щоб увійти й користуватися автозаповненням." }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 998fcf560e2..2d87e120753 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "Mật khẩu chính không hợp lệ" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "Mật khẩu tập tin không hợp lệ, vui lòng sử dụng mật khẩu bạn đã nhập khi xuất tập tin." }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "Truy cập bị từ chối. Bạn không có quyền xem trang này." }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Đã cài đặt tiện ích mở rộng Bitwarden!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Mở tiện ích mở rộng để đăng nhập và bắt đầu tự động điền." }, diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index e9b81cd5a07..7acd946f616 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "无效的主密码" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "无效的文件密码,请使用您创建导出文件时输入的密码。" }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "访问被拒绝。您没有权限查看此页面。" }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden 扩展已安装!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "打开扩展以登录并开始自动填充。" }, diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 78f1665121a..bee3c8ea76a 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1499,6 +1499,15 @@ "invalidMasterPassword": { "message": "無效的主密碼" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "invalidFilePassword": { "message": "檔案密碼無效,請使用您當初匯出檔案時輸入的密碼。" }, @@ -7034,6 +7043,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "accessDenied": { "message": "拒絕存取。您沒有檢視此頁面的權限。" }, @@ -11195,6 +11222,12 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, + "openTheBitwardenExtension": { + "message": "Open the Bitwarden extension" + }, + "bitwardenExtensionInstalledOpenExtension": { + "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." }, From 6c2791338ef60e83571dae721fb68d9279a17751 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:12:19 +0000 Subject: [PATCH 50/83] Autosync the updated translations (#16714) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 27 +++++ apps/browser/src/_locales/az/messages.json | 27 +++++ apps/browser/src/_locales/be/messages.json | 27 +++++ apps/browser/src/_locales/bg/messages.json | 31 +++++- apps/browser/src/_locales/bn/messages.json | 27 +++++ apps/browser/src/_locales/bs/messages.json | 27 +++++ apps/browser/src/_locales/ca/messages.json | 27 +++++ apps/browser/src/_locales/cs/messages.json | 27 +++++ apps/browser/src/_locales/cy/messages.json | 27 +++++ apps/browser/src/_locales/da/messages.json | 27 +++++ apps/browser/src/_locales/de/messages.json | 105 +++++++++++------- apps/browser/src/_locales/el/messages.json | 27 +++++ apps/browser/src/_locales/en_GB/messages.json | 27 +++++ apps/browser/src/_locales/en_IN/messages.json | 27 +++++ apps/browser/src/_locales/es/messages.json | 27 +++++ apps/browser/src/_locales/et/messages.json | 27 +++++ apps/browser/src/_locales/eu/messages.json | 27 +++++ apps/browser/src/_locales/fa/messages.json | 27 +++++ apps/browser/src/_locales/fi/messages.json | 27 +++++ apps/browser/src/_locales/fil/messages.json | 27 +++++ apps/browser/src/_locales/fr/messages.json | 27 +++++ apps/browser/src/_locales/gl/messages.json | 27 +++++ apps/browser/src/_locales/he/messages.json | 27 +++++ apps/browser/src/_locales/hi/messages.json | 27 +++++ apps/browser/src/_locales/hr/messages.json | 27 +++++ apps/browser/src/_locales/hu/messages.json | 31 +++++- apps/browser/src/_locales/id/messages.json | 27 +++++ apps/browser/src/_locales/it/messages.json | 27 +++++ apps/browser/src/_locales/ja/messages.json | 27 +++++ apps/browser/src/_locales/ka/messages.json | 29 ++++- apps/browser/src/_locales/km/messages.json | 27 +++++ apps/browser/src/_locales/kn/messages.json | 27 +++++ apps/browser/src/_locales/ko/messages.json | 27 +++++ apps/browser/src/_locales/lt/messages.json | 27 +++++ apps/browser/src/_locales/lv/messages.json | 31 +++++- apps/browser/src/_locales/ml/messages.json | 27 +++++ apps/browser/src/_locales/mr/messages.json | 27 +++++ apps/browser/src/_locales/my/messages.json | 27 +++++ apps/browser/src/_locales/nb/messages.json | 27 +++++ apps/browser/src/_locales/ne/messages.json | 27 +++++ apps/browser/src/_locales/nl/messages.json | 31 +++++- apps/browser/src/_locales/nn/messages.json | 27 +++++ apps/browser/src/_locales/or/messages.json | 27 +++++ apps/browser/src/_locales/pl/messages.json | 31 +++++- apps/browser/src/_locales/pt_BR/messages.json | 31 +++++- apps/browser/src/_locales/pt_PT/messages.json | 31 +++++- apps/browser/src/_locales/ro/messages.json | 27 +++++ apps/browser/src/_locales/ru/messages.json | 31 +++++- apps/browser/src/_locales/si/messages.json | 27 +++++ apps/browser/src/_locales/sk/messages.json | 31 +++++- apps/browser/src/_locales/sl/messages.json | 27 +++++ apps/browser/src/_locales/sr/messages.json | 27 +++++ apps/browser/src/_locales/sv/messages.json | 43 +++++-- apps/browser/src/_locales/ta/messages.json | 27 +++++ apps/browser/src/_locales/te/messages.json | 27 +++++ apps/browser/src/_locales/th/messages.json | 27 +++++ apps/browser/src/_locales/tr/messages.json | 59 +++++++--- apps/browser/src/_locales/uk/messages.json | 27 +++++ apps/browser/src/_locales/vi/messages.json | 31 +++++- apps/browser/src/_locales/zh_CN/messages.json | 27 +++++ apps/browser/src/_locales/zh_TW/messages.json | 27 +++++ 61 files changed, 1731 insertions(+), 84 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 84cece88d16..e8a37262993 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "كلمة المرور الرئيسية غير صالحة" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "نفذ وقت الخزانة" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "خطأ" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index cc13e47a187..c877b87f90f 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Yararsız ana parol" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Seyf vaxtının bitməsi" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Xəta" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index c5a94d223e2..fb645e0b815 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Памылковы асноўны пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Час чакання сховішча" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Памылка" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index b9089a0e807..5d6b1b38380 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -551,11 +551,11 @@ "message": "Нулиране на търсенето" }, "archiveNoun": { - "message": "Archive", + "message": "Архив", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архивиране", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Грешна главна парола" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Време за достъп" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Грешка" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index db306f0cfe6..66d3fbe598a 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "অবৈধ মূল পাসওয়ার্ড" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "ভল্টের সময়সীমা" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index c33d183c1e7..39df44ca95e 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 8abbf14100d..7f897e9cb4c 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Contrasenya mestra no vàlida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Temps d'espera de la caixa forta" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 59d17582c3d..eaf86015839 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Chybné hlavní heslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Časový limit trezoru" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Bude exportován jen trezor organizace přidružený k $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Bude exportován jen trezor organizace přidružený k $ORGANIZATION$. Položky mých sbírek nebudou zahrnuty.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Chyba" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index f129b8ce771..ded0ad406df 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Prif gyfrinair annilys" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Cloi'r gell" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Gwall" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 452d9d9423c..04d287947ee 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Ugyldig hovedadgangskode" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Boks timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Fejl" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index fc3707f03ee..bc86e48476a 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -551,11 +551,11 @@ "message": "Suche zurücksetzen" }, "archiveNoun": { - "message": "Archive", + "message": "Archiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivieren", "description": "Verb" }, "unarchive": { @@ -568,7 +568,7 @@ "message": "Kein Eintrag im Archiv" }, "noItemsInArchiveDesc": { - "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Autofill-Vorschlägen ausgeschlossen." + "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen." }, "itemSentToArchive": { "message": "Eintrag an das Archiv gesendet" @@ -580,7 +580,7 @@ "message": "Eintrag archivieren" }, "archiveItemConfirmDesc": { - "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen und Autofill-Vorschlägen ausgeschlossen. Sind Sie sicher, dass Sie diesen Eintrag archivieren möchten?" + "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen. Bist du sicher, dass du diesen Eintrag archivieren möchtest?" }, "edit": { "message": "Bearbeiten" @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Ungültiges Master-Passwort" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Tresor-Timeout" }, @@ -1101,7 +1110,7 @@ "message": "Speichern" }, "notificationViewAria": { - "message": "$ITEMNAME$ Ansicht, wird in einem neuen Fenster geöffnet", + "message": "$ITEMNAME$ anzeigen, öffnet sich in neuem Fenster", "placeholders": { "itemName": { "content": "$1" @@ -1110,7 +1119,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "Neuer Eintrag, wird in einem neuen Fenster geöffnet", + "message": "Neuer Eintrag, öffnet sich in neuem Fenster", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1212,10 +1221,10 @@ "description": "Detailed error message shown when saving login details fails." }, "changePasswordWarning": { - "message": "Nachdem Sie Ihr Passwort geändert haben, müssen Sie sich mit Ihrem neuen Passwort anmelden. Aktive Sitzungen auf anderen Geräten werden innerhalb einer Stunde abgemeldet." + "message": "Nachdem du dein Passwort geändert hast, musst du dich mit deinem neuen Passwort anmelden. Aktive Sitzungen auf anderen Geräten werden innerhalb einer Stunde abgemeldet." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Ändern Sie Ihr Master-Passwort, um die Konto­wiederherstellung abzuschließen." + "message": "Ändere dein Master-Passwort, um die Kontowiederherstellung abzuschließen." }, "enableChangedPasswordNotification": { "message": "Nach dem Aktualisieren bestehender Zugangsdaten fragen" @@ -1410,7 +1419,7 @@ "message": "Funktion nicht verfügbar" }, "legacyEncryptionUnsupported": { - "message": "Alte Verschlüsselung wird nicht mehr unterstützt. Bitte wenden Sie sich an den Support, um Ihr Konto wiederherzustellen." + "message": "Die veraltete Verschlüsselung wird nicht mehr unterstützt. Bitte kontaktiere den Support, um dein Konto wiederherzustellen." }, "premiumMembership": { "message": "Premium-Mitgliedschaft" @@ -1641,13 +1650,13 @@ "message": "Vorschläge zum Auto-Ausfüllen" }, "autofillSpotlightTitle": { - "message": "Autofill-Vorschläge leicht finden" + "message": "Auto-Ausfüllen-Vorschläge einfach finden" }, "autofillSpotlightDesc": { - "message": "Deaktivieren Sie die Autofill-Einstellungen des Browsers, damit sie nicht mit Bitwarden in Konflikt geraten." + "message": "Deaktiviere die Auto-Ausfüllen-Einstellungen deines Browsers, damit sie nicht mit Bitwarden in Konflikt geraten." }, "turnOffBrowserAutofill": { - "message": "$BROWSER$ Auto-Ausfüllen deaktivieren", + "message": "Deaktiviere das automatische Ausfüllen von $BROWSER$", "placeholders": { "browser": { "content": "$1", @@ -1656,7 +1665,7 @@ } }, "turnOffAutofill": { - "message": "Auto-Ausfüllen deaktivieren" + "message": "Deaktiviere das automatische Ausfüllen" }, "showInlineMenuLabel": { "message": "Vorschläge zum Auto-Ausfüllen in Formularfeldern anzeigen" @@ -1972,11 +1981,11 @@ "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "Neuen Text senden", + "message": "Neues Text-Send", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "Neue Datei senden", + "message": "Neues Datei-Send", "description": "Header for new file send" }, "editItemHeaderLogin": { @@ -2000,11 +2009,11 @@ "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Textversand bearbeiten", + "message": "Text-Send bearbeiten", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Dateiversand bearbeiten", + "message": "Datei-Send bearbeiten", "description": "Header for edit file send" }, "viewItemHeaderLogin": { @@ -2232,7 +2241,7 @@ "message": "Gebe deinen PIN-Code für das Entsperren von Bitwarden ein. Deine PIN-Einstellungen werden zurückgesetzt, wenn du dich vollständig von der Anwendung abmeldest." }, "setPinCode": { - "message": "Sie können diese PIN verwenden, um Bitwarden zu entsperren. Ihre PIN wird zurückgesetzt, wenn Sie sich jemals vollständig von der Anwendung abmelden." + "message": "Du kannst diese PIN verwenden, um Bitwarden zu entsperren. Deine PIN wird zurückgesetzt, wenn du dich einmal vollständig aus der Anwendung abmeldest." }, "pinRequired": { "message": "PIN-Code ist erforderlich." @@ -2566,7 +2575,7 @@ "message": "Karten-Eintragstypen können nicht importiert werden" }, "restrictCardTypeImportDesc": { - "message": "Eine von 1 oder mehreren Organisationen festgelegte Richtlinie verhindert den Import von Karten in Ihre Tresore." + "message": "Eine von einer oder mehreren Organisationen festgelegte Richtlinie verhindert, dass du Karten in deinen Tresor importieren kannst." }, "domainsTitle": { "message": "Domains", @@ -2650,7 +2659,7 @@ } }, "atRiskChangePrompt": { - "message": "Ihr Passwort für diese Website ist gefährdet. $ORGANIZATION$ hat Sie aufgefordert, es zu ändern.", + "message": "Dein Passwort für diese Website ist gefährdet. $ORGANIZATION$ hat dich aufgefordert, es zu ändern.", "placeholders": { "organization": { "content": "$1", @@ -2660,7 +2669,7 @@ "description": "Notification body when a login triggers an at-risk password change request and the change password domain is known." }, "atRiskNavigatePrompt": { - "message": "$ORGANIZATION$ möchte, dass Sie dieses Passwort ändern, da es gefährdet ist. Gehen Sie zu Ihren Kontoeinstellungen, um das Passwort zu ändern.", + "message": "$ORGANIZATION$ möchte, dass du dieses Passwort änderst, da es gefährdet ist. Gehe zu deinen Kontoeinstellungen, um das Passwort zu ändern.", "placeholders": { "organization": { "content": "$1", @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Nur der mit $ORGANIZATION$ verknüpfte Organisations-Tresor wird exportiert.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Nur der mit $ORGANIZATION$ verknüpfte Organisations-Tresor wird exportiert. Meine Eintrags-Sammlungen werden nicht eingeschlossen.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Fehler" }, @@ -3543,7 +3570,7 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Sie haben einen Versuch mit Zugangsdaten von einem anderen Gerät abgelehnt. Wenn Sie das waren, versuchen Sie, sich mit dem Gerät erneut anzumelden." + "message": "Du hast einen Zugangsdaten-Versuch von einem anderen Gerät abgelehnt. Falls du es warst, versuche dich erneut mit diesem Gerät anzumelden." }, "device": { "message": "Gerät" @@ -3757,7 +3784,7 @@ "message": "Anmeldung kann nicht abgeschlossen werden" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { - "message": "Sie müssen sich auf einem vertrauenswürdigen Gerät anmelden oder Ihren Administrator bitten, Ihnen ein Passwort zuzuweisen." + "message": "Du musst dich auf einem vertrauenswürdigen Gerät anmelden oder deinen Administrator bitten, dir ein Passwort zuzuweisen." }, "ssoIdentifierRequired": { "message": "SSO-Kennung der Organisation erforderlich." @@ -3833,13 +3860,13 @@ "message": "Organisation ist nicht vertrauenswürdig" }, "emergencyAccessTrustWarning": { - "message": "Zur Sicherheit Ihres Kontos bestätigen Sie nur, wenn Sie diesem Benutzer Notfallzugriff gewährt haben und sein Fingerabdruck mit dem in seinem Konto angezeigten übereinstimmt" + "message": "Zur Sicherheit deines Kontos bestätige nur, wenn du diesem Benutzer Notfallzugriff gewährt hast und sein Fingerabdruck mit dem übereinstimmt, was in seinem Konto angezeigt wird" }, "orgTrustWarning": { - "message": "Zur Sicherheit Ihres Kontos fahren Sie nur fort, wenn Sie Mitglied dieser Organisation sind, die Kontowiederherstellung aktiviert haben und der unten angezeigte Fingerabdruck mit dem Fingerabdruck der Organisation übereinstimmt." + "message": "Fahre zur Sicherheit deines Kontos nur fort, wenn du ein Mitglied dieser Organisation bist, die Kontowiederherstellung aktiviert hast und der unten angezeigte Fingerabdruck mit dem Fingerabdruck der Organisation übereinstimmt." }, "orgTrustWarning1": { - "message": "Diese Organisation hat eine Enterprise-Richtlinie, die Sie in die Kontowiederherstellung registriert. Die Registrierung ermöglicht es den Administratoren der Organisation, Ihr Passwort zu ändern. Fahren Sie nur fort, wenn Sie diese Organisation erkennen und die unten angezeigte Fingerabdruck-Phrase mit dem Fingerabdruck der Organisation übereinstimmt." + "message": "Diese Organisation hat eine Enterprise-Richtlinie, die dich für die Kontowiederherstellung registriert. Die Registrierung ermöglicht es den Organisations-Administratoren, dein Passwort zu ändern. Fahre nur fort, wenn du diese Organisation erkennst und die unten angezeigte Fingerabdruck-Phrase mit dem Fingerabdruck der Organisation übereinstimmt." }, "trustUser": { "message": "Benutzer vertrauen" @@ -4424,7 +4451,7 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "Die URI-Übereinstimmungserkennung ist die Methode, mit der Bitwarden Auto-Ausfüllen-Vorschläge erkennt.", + "message": "Die URI-Übereinstimmungserkennung ist die Methode, mit der Bitwarden Vorschläge zum automatischen Ausfüllen identifiziert.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { @@ -5562,7 +5589,7 @@ "message": "Favoriten-Einträge für einfachen Zugriff" }, "hasItemsVaultNudgeBodyThree": { - "message": "Durchsuchen Sie Ihren Tresor nach etwas anderem" + "message": "Durchsuche deinen Tresor nach etwas anderem" }, "newLoginNudgeTitle": { "message": "Spare Zeit mit Auto-Ausfüllen" @@ -5578,7 +5605,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "hinzu, damit diese Zugangsdaten als Auto-Ausfüllen-Vorschlag erscheinen.", + "message": "damit diese Zugangsdaten als Vorschlag zum automatischen Ausfüllen erscheinen.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5586,30 +5613,30 @@ "message": "Nahtlose Online-Kaufabwicklung" }, "newCardNudgeBody": { - "message": "Mit Karten können Sie Zahlungsformulare einfach, sicher und genau automatisch ausfüllen." + "message": "Mit Karten kannst du Zahlungsformulare sicher und präzise einfach automatisch ausfüllen." }, "newIdentityNudgeTitle": { "message": "Erstellung von Konten vereinfachen" }, "newIdentityNudgeBody": { - "message": "Mit Identitäten können Sie lange Registrierungs- oder Kontaktformulare schnell per Autofill ausfüllen." + "message": "Mit Identitäten kannst du lange Registrierungs- oder Kontaktformulare schnell automatisch ausfüllen." }, "newNoteNudgeTitle": { - "message": "Halten Sie Ihre sensiblen Daten sicher" + "message": "Halte deine sensiblen Daten sicher" }, "newNoteNudgeBody": { - "message": "Mit Notizen können Sie sensible Daten wie Bank- oder Versicherungsdaten sicher speichern." + "message": "Mit Notizen kannst du sensible Daten wie Bank- oder Versicherungsinformationen sicher speichern." }, "newSshNudgeTitle": { "message": "Entwickler-freundlicher SSH-Zugriff" }, "newSshNudgeBodyOne": { - "message": "Speichern Sie Ihre Schlüssel und verbinden Sie sich mit dem SSH-Agenten für eine schnelle, verschlüsselte Authentifizierung.", + "message": "Speichere deine Schlüssel und verbinde dich mit dem SSH-Agenten für eine schnelle, verschlüsselte Authentifizierung.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Erfahren Sie mehr über den SSH-Agenten", + "message": "Erfahre mehr über den SSH-Agenten", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, @@ -5622,22 +5649,22 @@ "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyTwo": { - "message": "damit Sie Ihre Zugangsdaten sicher halten können.", + "message": ", um dir zu helfen, deine Zugangsdaten sicher zu halten.", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Erstellen Sie ganz einfach starke und eindeutige Passwörter, indem Sie auf die Schaltfläche ‚Passwort generieren‘ klicken, damit Sie Ihre Zugangsdaten sicher halten können.", + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um dir zu helfen, deine Zugangsdaten sicher zu halten.", "description": "Aria label for the body content of the generator nudge" }, "aboutThisSetting": { "message": "Über diese Einstellung" }, "permitCipherDetailsDescription": { - "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu bestimmen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um Ihr Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn Sie diesen Dienst nutzen." + "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu erkennen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um dein Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn du diesen Dienst nutzt." }, "noPermissionsViewPage": { - "message": "Sie haben keine Berechtigung, diese Seite anzusehen. Versuchen Sie, sich mit einem anderen Konto anzumelden." + "message": "Du hast keine Berechtigung, diese Seite anzuzeigen. Versuche, dich mit einem anderen Konto anzumelden." }, "wasmNotSupported": { "message": "WebAssembly wird in Ihrem Browser nicht unterstützt oder ist nicht aktiviert. WebAssembly ist erforderlich, um die Bitwarden-App zu verwenden.", diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 9c23b511611..139899b10ac 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Μη έγκυρος κύριος κωδικός πρόσβασης" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Χρόνος Λήξης Vault" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Σφάλμα" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index be548498512..8dc87aa722a 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index b6f175afcfe..bf8e62e4f83 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 95d31915e2d..27404fdc5f6 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Contraseña maestra no válida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Tiempo de espera de la caja fuerte" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index e50aad7c70f..a4fb0892e46 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Vale ülemparool" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Hoidla ajalõpp" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Viga" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index a6abe270e38..cc6b510f37e 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Pasahitz nagusi baliogabea" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Kutxa gotorraren itxaronaldia" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Akatsa" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index d8634a88bd0..4ec4d513039 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "کلمه عبور اصلی نامعتبر است" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "متوقف شدن گاو‌صندوق" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "خطا" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index cfe4bc3504d..5edc9e84c63 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Virheellinen pääsalasana" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Holvin aikakatkaisu" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Virhe" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 678cf714915..2afa450a7d0 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Hindi wasto ang master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Mali" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 031bca7c44c..c74f0248693 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Mot de passe principal invalide" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Délai d'expiration du coffre" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Erreur" }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 217c3d37176..421adf16a2f 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Contrasinal mestre non válido" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Tempo de espera da caixa forte" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Erro" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index d2a8db5fdd7..affba06eafd 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "סיסמה ראשית שגויה" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "פסק זמן לכספת" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "שגיאה" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 35e69470e4c..323d72f802d 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "अमान्य मास्टर पासवर्ड" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "वॉल्ट मध्यांतर" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "एरर" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index bb76ea74b82..7d83ecdab1d 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Neispravna glavna lozinka" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Istek trezora" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Pogreška" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 6717b18d38e..def94f2f283 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -551,11 +551,11 @@ "message": "Keresés visszaállítása" }, "archiveNoun": { - "message": "Archive", + "message": "Archívum", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archívum", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Hibás mesterjelszó" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Széf időkifutás" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Csak a $ORGANIZATION$ szervezetehez kapcsolódó szervezeti széf kerül exportálásra.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Csak a $ORGANIZATION$ szervezethez kapcsolódó szervezeti széf kerül exportálásra. A saját elem gyűjtemények nem lesznek benne.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Hiba" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 9d3c2817bf1..80886711095 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Sandi utama tidak valid" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Batas Waktu Brankas" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Galat" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index ccabe3cb55c..c9a51f416e4 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Password principale errata" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Timeout cassaforte" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Errore" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index df19cf23bc3..0fa768e3716 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "マスターパスワードが間違っています" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "保管庫のタイムアウト" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "エラー" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index b0c65bc8a37..13f3da81404 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -162,7 +162,7 @@ "message": "Copy passport number" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "დააკოპირეთ ლიცენზიის ნომერი" }, "copyPrivateKey": { "message": "Copy private key" @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "შეცდომა" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 9646960000a..dcd86eb4337 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 94f276e5b73..16e6cc2a631 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "ಅಮಾನ್ಯ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "ವಾಲ್ಟ್ ಕಾಲಾವಧಿ" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index bffa0c34ee6..9b80dae4d7c 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "잘못된 마스터 비밀번호" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "보관함 시간 제한" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "오류" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 55555fe9b4f..478164a7f9c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Neteisingas pagrindinis slaptažodis" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Atsijungta nuo saugyklos" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Klaida" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index a70aded2ed1..3cf4fef05e5 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -551,11 +551,11 @@ "message": "Atiestatīt meklēšanu" }, "archiveNoun": { - "message": "Archive", + "message": "Arhīvs", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arhivēt", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Nederīga galvenā parole" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Glabātavas noildze" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Kļūda" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 4ebcd08bb77..cd99f32714e 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "അസാധുവായ പ്രാഥമിക പാസ്‌വേഡ്" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "വാൾട് ടൈംഔട്ട്" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 4bc66b4e85b..e639bae2662 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "अवैध मुख्य पासवर्ड" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 9646960000a..dcd86eb4337 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 113c28c65d3..b01d5a5ebbf 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Ugyldig hovedpassord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Tidsavbrudd i hvelvet" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Feil" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 9646960000a..dcd86eb4337 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 155886506b0..521d9788e0e 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -551,11 +551,11 @@ "message": "Zoekopdracht resetten" }, "archiveNoun": { - "message": "Archive", + "message": "Archief", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archiveren", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Ongeldig hoofdwachtwoord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Time-out van de kluis" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Alleen de organisatiekluis die gekoppeld is aan $ORGANIZATION$ wordt geëxporteerd.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Exporteert alleen de organisatiekluis van $ORGANIZATION$. Geen persoonlijke kluis-items.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Fout" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 9646960000a..dcd86eb4337 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 9646960000a..dcd86eb4337 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index e455a0c7d2e..8d96ccaffc6 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -551,11 +551,11 @@ "message": "Zresetuj wyszukiwanie" }, "archiveNoun": { - "message": "Archive", + "message": "Archiwum", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archiwizuj", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Hasło główne jest nieprawidłowe" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Blokowanie sejfu" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Błąd" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 0161aa77ea7..746c04bdf0f 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Senha mestra inválida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Cofre - tempo esgotado" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Erro" }, @@ -3645,7 +3672,7 @@ "message": "Lembrar deste dispositivo para permanecer conectado" }, "manageDevices": { - "message": "Manage devices" + "message": "Gerenciar dispositivos" }, "currentSession": { "message": "Current session" @@ -3688,7 +3715,7 @@ "message": "Needs approval" }, "devices": { - "message": "Devices" + "message": "Dispositivos" }, "accessAttemptBy": { "message": "Access attempt by $EMAIL$", diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index cc90d7653c9..6782468d4b0 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -551,11 +551,11 @@ "message": "Repor pesquisa" }, "archiveNoun": { - "message": "Archive", + "message": "Arquivo", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arquivar", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Palavra-passe mestra inválida" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Tempo limite do cofre" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Erro" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 4cc552b11ab..2dcf2fb6181 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Parolă principală incorectă" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Expirare seif" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Eroare" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 49cdcf62161..d4c83aa7ccf 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -551,11 +551,11 @@ "message": "Сбросить поиск" }, "archiveNoun": { - "message": "Archive", + "message": "Архив", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архивировать", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Неверный мастер-пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Тайм-аут хранилища" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$. Коллекции Мои элементы включены не будут.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Ошибка" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 6153cb49b6e..8218a5d8787 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "වලංගු නොවන ප්රධාන මුරපදය" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "සුරක්ෂිතාගාරය වේලාව" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index da1ba9427ea..c6e79e241aa 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -551,11 +551,11 @@ "message": "Resetovať vyhľadávanie" }, "archiveNoun": { - "message": "Archive", + "message": "Archív", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Archivovať", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Neplatné hlavné heslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Časový limit pre trezor" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Exportuje sa len trezor organizácie spojený s $ORGANIZATION$.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Exportuje sa len trezor organizácie spojený s $ORGANIZATION$. Moje zbierky položiek nebudú zahrnuté.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Chyba" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 596ee118eab..23327bbb1ae 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Napačno glavno geslo" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Zakleni trezor, ko preteče toliko časa:" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Napaka" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index c3c9cac383d..a32d2c33504 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Погрешна главна лозинка" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Тајмаут сефа" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Грешка" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 02b334d9412..a84efb53464 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -551,11 +551,11 @@ "message": "Nollställ sökning" }, "archiveNoun": { - "message": "Archive", + "message": "Arkiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arkivera", "description": "Verb" }, "unarchive": { @@ -568,7 +568,7 @@ "message": "Inga objekt i arkivet" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Arkiverade objekt kommer att visas här och kommer att uteslutas från allmänna sökresultat och förslag för autofyll." }, "itemSentToArchive": { "message": "Objekt skickat till arkiv" @@ -580,7 +580,7 @@ "message": "Arkivera objekt" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Arkiverade objekt är exkluderade från allmänna sökresultat och förslag för autofyll. Är du säker på att du vill arkivera detta objekt?" }, "edit": { "message": "Redigera" @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Ogiltigt huvudlösenord" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Valvets tidsgräns" }, @@ -1972,11 +1981,11 @@ "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "Ny textsändning", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "Ny filsändning", "description": "Header for new file send" }, "editItemHeaderLogin": { @@ -2000,11 +2009,11 @@ "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "Redigera textsändning", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "Redigera filsändning", "description": "Header for edit file send" }, "viewItemHeaderLogin": { @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Endast organisationsvalvet som är associerat med $ORGANIZATION$ kommer att exporteras.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Endast organisationsvalvet som associeras med $ORGANIZATION$ kommer att exporteras. Mina objektsamlingar kommer inte att inkluderas.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Fel" }, diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index c5a13db193e..8d1f1ccf87f 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "தவறான முதன்மை கடவுச்சொல்" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "வால்ட் காலக்கெடு" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "பிழை" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 9646960000a..dcd86eb4337 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Invalid master password" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Vault timeout" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 4eed06d0a9a..064b70c26ec 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "รหัสผ่านหลักไม่ถูกต้อง" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "ระยะเวลาล็อกตู้เซฟ" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Error" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index f2acc401a2c..040c92f01c4 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -362,7 +362,7 @@ "message": "Ücretsiz Bitwarden Aile" }, "freeBitwardenFamiliesPageDesc": { - "message": "Ücretsiz Bitwarden Aile Paketi’nden faydalanmaya hak kazandınız. Bu teklifi bugün web uygulaması üzerinden kullanın." + "message": "Ücretsiz Bitwarden Aileleri için uygunsun. Bu teklifi bugün web uygulamasında kullan." }, "version": { "message": "Sürüm" @@ -562,25 +562,25 @@ "message": "Arşivden çıkar" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Arşivdeki kayıtlar" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Arşivde kayıt yok" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Arşivlenmiş kayıtlar burada görünecek ve genel arama sonuçlarından ile otomatik doldurma önerilerinden hariç tutulacaktır." }, "itemSentToArchive": { - "message": "Item sent to archive" + "message": "Kayıt arşive gönderildi" }, "itemRemovedFromArchive": { - "message": "Item removed from archive" + "message": "Kayıt arşivden çıkarıldı" }, "archiveItem": { - "message": "Archive item" + "message": "Kaydı arşivle" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Arşivlenmiş kayıtlar genel arama sonuçları ve otomatik doldurma önerilerinden hariç tutulur. Bu kaydı arşivlemek istediğine emin misin?" }, "edit": { "message": "Düzenle" @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Geçersiz ana parola" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Kasa zaman aşımı" }, @@ -880,13 +889,13 @@ "message": "Mevcut web sayfasındaki kimlik doğrulayıcı QR kodunu tarayın" }, "totpHelperTitle": { - "message": "2 adımlı doğrulamayı sorunsuz hale getirin" + "message": "2 adımlı doğrulamayı sorunsuz hale getir" }, "totpHelper": { - "message": "Bitwarden, 2 adımlı doğrulama kodlarını saklayabilir ve otomatik olarak doldurabilir. Anahtarı kopyalayıp bu alana yapıştırın." + "message": "Bitwarden 2 adımlı doğrulama kodlarını saklayabilir ve doldurabilir. Anahtarı bu alana kopyala ve yapıştır." }, "totpHelperWithCapture": { - "message": "Bitwarden, iki adımlı doğrulama kodlarını saklayabilir ve otomatik olarak doldurabilir. Bu web sitesinin doğrulayıcı QR kodunun ekran görüntüsünü almak için kamera simgesini seçin veya anahtarı bu alana kopyalayıp yapıştırın." + "message": "Bitwarden 2 adımlı doğrulama kodlarını saklayabilir ve doldurabilir. Bu web sitesinin kimlik doğrulayıcı QR kodunun ekran görüntüsünü almak için kamera simgesini seç veya anahtarı bu alana kopyala ve yapıştır." }, "learnMoreAboutAuthenticators": { "message": "Kimlik doğrulayıcılar hakkında bilgi alın" @@ -1597,7 +1606,7 @@ "message": "Şirket içinde barındırılan ortam" }, "selfHostedBaseUrlHint": { - "message": "Yerel sunucunuzda barındırılan Bitwarden kurulumunuzun temel URL’sini belirtin. Örnek: https://bitwarden.sirketiniz.com" + "message": "Şirket içinde barındırılan Bitwarden kurulumunun temel URL’sini belirt. Örnek: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { "message": "İleri düzey yapılandırma için her hizmetin taban URL'sini bağımsız olarak belirleyebilirsiniz." @@ -2563,7 +2572,7 @@ "message": "Bir kuruluş ilkesi, kayıtları kişisel kasanıza içe aktarmayı engelledi." }, "restrictCardTypeImport": { - "message": "Kart öge türleri içe aktarılamıyor" + "message": "Kart kayıt türleri içe aktarılamıyor" }, "restrictCardTypeImportDesc": { "message": "1 veya daha fazla kuruluş tarafından belirlenen bir ilke, kasalarınıza kart aktarmanızı engelliyor." @@ -2694,14 +2703,14 @@ "message": "Risk altındaki parolaları inceleyin" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Organizasyonunuzun parolaları zayıf, tekrar kullanılmış ve/veya açığa çıkmış olduğu için risk altındadır.", + "message": "Kuruluş parolaların zayıf, yeniden kullanılmış ve/veya ele geçirilmiş olduğundan risk altındadır.", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Risk altındaki hesap listesinin illüstrasyonu." }, "generatePasswordSlideDesc": { - "message": "Risk altındaki sitede Bitwarden otomatik doldurma menüsü ile hızlıca güçlü ve benzersiz bir parola oluşturun.", + "message": "Riskli sitede Bitwarden otomatik doldurma menüsünü kullanarak hızlıca güçlü ve benzersiz bir parola oluştur.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Yalnızca $ORGANIZATION$ ile ilişkili kuruluş kasası dışa aktarılacaktır.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Yalnızca $ORGANIZATION$ ile ilişkili kuruluş kasası dışa aktarılacaktır. Kayıt koleksiyonlarım dahil edilmeyecektir.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Hata" }, @@ -5634,7 +5661,7 @@ "message": "Bu ayar hakkında" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "Bitwarden, deneyimini iyileştirmek için hangi simgenin veya parola değiştirme URL’sinin kullanılacağını belirlemek amacıyla kaydedilmiş hesap URI’lerini kullanır. Bu hizmeti kullandığında hiçbir bilgi toplanmaz veya kaydedilmez." }, "noPermissionsViewPage": { "message": "Bu sayfayı görüntüleme izniniz yok. Farklı bir hesapla giriş yapmayı deneyin." diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index a99c585978b..fb64a91330d 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Неправильний головний пароль" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Час очікування сховища" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Помилка" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 3a11f0f237f..7b74d5c60b8 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -551,11 +551,11 @@ "message": "Đặt lại tìm kiếm" }, "archiveNoun": { - "message": "Archive", + "message": "Lưu trữ", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Lưu trữ", "description": "Verb" }, "unarchive": { @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "Mật khẩu chính không hợp lệ" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "Đóng kho sau" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "Lỗi" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 77a3855633c..2b7a34aa1a2 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "无效的主密码" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "密码库超时时间" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "错误" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 23564473318..5f6defcfe24 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -733,6 +733,15 @@ "invalidMasterPassword": { "message": "無效的主密碼" }, + "invalidMasterPasswordConfirmEmailAndHost": { + "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "placeholders": { + "host": { + "content": "$1", + "example": "vault.bitwarden.com" + } + } + }, "vaultTimeout": { "message": "密碼庫逾時時間" }, @@ -3198,6 +3207,24 @@ } } }, + "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, "error": { "message": "錯誤" }, From e14c8c6a9ccc01635669ed28b9b267784eac4a60 Mon Sep 17 00:00:00 2001 From: Mick Letofsky <mletofsky@bitwarden.com> Date: Fri, 3 Oct 2025 16:48:01 +0200 Subject: [PATCH 51/83] [PM-26337] Create a Claude markdown file (#16676) * Initial claude markdown with lots of help from the team. --- .github/workflows/lint.yml | 1 + CLAUDE.md | 90 ++++++++++++++++++++++++++++++++++++++ apps/browser/CLAUDE.md | 22 ++++++++++ apps/cli/CLAUDE.md | 13 ++++++ apps/desktop/CLAUDE.md | 11 +++++ apps/web/CLAUDE.md | 13 ++++++ 6 files changed, 150 insertions(+) create mode 100644 CLAUDE.md create mode 100644 apps/browser/CLAUDE.md create mode 100644 apps/cli/CLAUDE.md create mode 100644 apps/desktop/CLAUDE.md create mode 100644 apps/web/CLAUDE.md diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7eab45e5b1b..738aef899f5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,6 +49,7 @@ jobs: ! -path "*/Cargo.toml" \ ! -path "*/Cargo.lock" \ ! -path "./apps/desktop/macos/*" \ + ! -path "*/CLAUDE.md" \ > tmp.txt diff <(sort .github/whitelist-capital-letters.txt) <(sort tmp.txt) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..0870553f8d3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,90 @@ +# Bitwarden Clients - Claude Code Configuration + +## Critical Rules + +- **NEVER** use code regions: If complexity suggests regions, refactor for better readability + +- **CRITICAL**: new encryption logic should not be added to this repo. + +- **NEVER** send unencrypted vault data to API services + +- **NEVER** commit secrets, credentials, or sensitive information. Follow the guidelines in `SECURITY.md`. + +- **NEVER** log decrypted data, encryption keys, or PII + - No vault data in error messages or console logs + +- **ALWAYS** Respect configuration files at the root and within each app/library (e.g., `eslint.config.mjs`, `jest.config.js`, `tsconfig.json`). + +## Mono-Repo Architecture + +This repository is organized as a **monorepo** containing multiple applications and libraries. The +main directories are: + +- `apps/` – Contains all application projects (e.g., browser, cli, desktop, web). Each app is + self-contained with its own configuration, source code, and tests. +- `libs/` – Contains shared libraries and modules used across multiple apps. Libraries are organized + by team name, domain, functionality (e.g., common, ui, platform, key-management). + +**Strict boundaries** must be maintained between apps and libraries. Do not introduce +cross-dependencies that violate the intended modular structure. Always consult and respect the +dependency rules defined in `eslint.config.mjs`, `nx.json`, and other configuration files. + +## Angular Architecture Patterns + +**Observable Data Services (ADR-0003):** + +- Services expose RxJS Observable streams for state management +- Components subscribe using `async` pipe (NOT explicit subscriptions in most cases) + Pattern: + +```typescript +// Service +private _folders = new BehaviorSubject<Folder[]>([]); +readonly folders$ = this._folders.asObservable(); + +// Component +folders$ = this.folderService.folders$; +// Template: <div *ngFor="let folder of folders$ | async"> +``` + +For explicit subscriptions, MUST use `takeUntilDestroyed()`: + +```typescript +constructor() { + this.observable$.pipe(takeUntilDestroyed()).subscribe(...); +} +``` + +**Angular Signals (ADR-0027):** + +Encourage the use of Signals **only** in Angular components and presentational services. + +Use **RxJS** for: + +- Services used across Angular and non-Angular clients +- Complex reactive workflows +- Interop with existing Observable-based code + +**NO TypeScript Enums (ADR-0025):** + +- Use const objects with type aliases instead +- Legacy enums exist but don't add new ones + +Pattern: + +```typescript +// ✅ DO +export const CipherType = Object.freeze({ + Login: 1, + SecureNote: 2, +} as const); +export type CipherType = (typeof CipherType)[keyof typeof CipherType]; + +// ❌ DON'T +enum CipherType { + Login = 1, + SecureNote = 2, +} +``` + +Example: `/libs/common/src/vault/enums/cipher-type.ts` diff --git a/apps/browser/CLAUDE.md b/apps/browser/CLAUDE.md new file mode 100644 index 00000000000..a718f5bfd7c --- /dev/null +++ b/apps/browser/CLAUDE.md @@ -0,0 +1,22 @@ +# Browser Extension - Critical Rules + +- **NEVER** use `chrome.*` or `browser.*` APIs directly in business logic + - Always use `BrowserApi` abstraction: `/apps/browser/src/platform/browser/browser-api.ts` + - Required for cross-browser compatibility (Chrome/Firefox/Safari/Opera) + +- **ALWAYS** use `BrowserApi.addListener()` for event listeners in popup context + - Safari requires manual cleanup to prevent memory leaks + - DON'T use native `chrome.*.addListener()` or `browser.*.addListener()` directly + +- **CRITICAL**: Safari has tab query bugs + - Use `BrowserApi.tabsQueryFirstCurrentWindowForSafari()` when querying current window tabs + - Safari can return tabs from multiple windows incorrectly + +## Manifest V3 + +- Extension uses Web Extension API Manifest V3 +- **Service workers replace background pages** + - Background context runs as service worker (can be terminated anytime) + - DON'T assume background page persists indefinitely + - Use message passing for communication between contexts + - `chrome.extension.getBackgroundPage()` returns `null` in MV3 diff --git a/apps/cli/CLAUDE.md b/apps/cli/CLAUDE.md new file mode 100644 index 00000000000..72ec31eaaf5 --- /dev/null +++ b/apps/cli/CLAUDE.md @@ -0,0 +1,13 @@ +# CLI - Critical Rules + +- **ALWAYS** output structured JSON when `process.env.BW_RESPONSE === "true"` + - Use Response objects (MessageResponse, ListResponse, etc.) from `/apps/cli/src/models/response/` + - DON'T write free-form text that breaks JSON parsing + +- **NEVER** use `console.log()` for output + - Use `CliUtils.writeLn()` to respect `BW_QUIET` and `BW_RESPONSE` environment variables + - Use the `ConsoleLogService` from the `ServiceContainer` + +- **ALWAYS** respect `BW_CLEANEXIT` environment variable + - Exit code 0 even on errors when `BW_CLEANEXIT` is set + - Required for scripting environments that need clean exits diff --git a/apps/desktop/CLAUDE.md b/apps/desktop/CLAUDE.md new file mode 100644 index 00000000000..65b1952851a --- /dev/null +++ b/apps/desktop/CLAUDE.md @@ -0,0 +1,11 @@ +# Desktop (Electron) - Critical Rules + +- **CRITICAL**: Separate main process vs renderer process contexts + - Main process: Node.js + Electron APIs (files in `/apps/desktop/src/main/`) + - Renderer process: Browser-like environment (Angular app files) + - Use IPC (Inter-Process Communication) for cross-process communication + +- **NEVER** import Node.js modules directly in renderer process +- **NEVER** import Angular modules in the main process + - Use preload scripts or IPC to access Node.js functionality + - See `/apps/desktop/src/*/preload.ts` files for patterns diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md new file mode 100644 index 00000000000..b9fe0055fe5 --- /dev/null +++ b/apps/web/CLAUDE.md @@ -0,0 +1,13 @@ +# Web Vault - Critical Rules + +- **NEVER** access browser extension APIs + - Web vault runs in standard browser context (no chrome._/browser._ APIs) + - DON'T import or use BrowserApi or extension-specific code + +- **ALWAYS** assume multi-tenant organization features + - Web vault supports enterprise organizations with complex permissions + - Use organization permission guards: `/apps/web/src/app/admin-console/organizations/guards/` + +- **CRITICAL**: All sensitive operations must work without local storage + - Web vault may run in environments that clear storage aggressively + - DON'T rely on localStorage/sessionStorage for security-critical data From 856903e66192ca9a4716380626d0236ee386d502 Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Fri, 3 Oct 2025 10:32:09 -0500 Subject: [PATCH 52/83] PM-26416 updated description of at-risk members card (#16724) --- apps/web/src/locales/en/messages.json | 4 ++-- .../app/dirt/access-intelligence/all-activity.component.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 939e703b040..1a8623a1973 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -205,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription":{ - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index 3e60347fcef..bb5416a9a13 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -28,7 +28,7 @@ <dirt-activity-card [title]="'atRiskMembers' | i18n" [cardMetrics]="'membersAtRiskCount' | i18n: totalCriticalAppsAtRiskMemberCount" - [metricDescription]="'membersAtRiskActivityDescription' | i18n" + [metricDescription]="'membersWithAccessToAtRiskItemsForCriticalApps' | i18n" navigationText="{{ 'viewAtRiskMembers' | i18n }}" navigationLink="{{ getLinkForRiskInsightsTab(RiskInsightsTabType.AllApps) }}" [showNavigationLink]="totalCriticalAppsAtRiskMemberCount > 0" From a9d7e13db94def4fbcd104aa15f7749f6a618d30 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:57:17 +0200 Subject: [PATCH 53/83] [deps]: Update actions/github-script action to v8 (#16425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com> --- .github/workflows/alert-ddg-files-modified.yml | 4 ++-- .github/workflows/auto-reply-discussions.yml | 6 +++--- .github/workflows/build-web.yml | 2 +- .github/workflows/publish-web.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/alert-ddg-files-modified.yml b/.github/workflows/alert-ddg-files-modified.yml index c341de045eb..d799cc2e248 100644 --- a/.github/workflows/alert-ddg-files-modified.yml +++ b/.github/workflows/alert-ddg-files-modified.yml @@ -30,7 +30,7 @@ jobs: - 'apps/desktop/src/services/encrypted-message-handler.service.ts' - name: Remove past BIT status comments - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | // Note: should match the first line of `message` in the communication steps @@ -67,7 +67,7 @@ jobs: - name: Comment on PR if monitored files changed if: steps.changed-files.outputs.monitored == 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const changedFiles = `${{ steps.changed-files.outputs.monitored_files }}`.split(' ').filter(file => file.trim() !== ''); diff --git a/.github/workflows/auto-reply-discussions.yml b/.github/workflows/auto-reply-discussions.yml index 83970ab3619..a6d7e9c6dcf 100644 --- a/.github/workflows/auto-reply-discussions.yml +++ b/.github/workflows/auto-reply-discussions.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Get discussion label and template name id: discussion-label - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const discussion = context.payload.discussion; @@ -29,7 +29,7 @@ jobs: - name: Get selected topic id: get_selected_topic - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: result-encoding: string script: | @@ -45,7 +45,7 @@ jobs: } - name: Reply or close Discussion - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: TEMPLATE_NAME: ${{ steps.discussion-label.outputs.template_name }} TOPIC: ${{ steps.get_selected_topic.outputs.result }} diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 18887ce8fbc..d623f08ebac 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -409,7 +409,7 @@ jobs: uses: bitwarden/gh-actions/azure-logout@main - name: Trigger web vault deploy using GitHub Run ID - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} script: | diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index a6f0f1be066..6446e625156 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -179,7 +179,7 @@ jobs: uses: bitwarden/gh-actions/azure-logout@main - name: Trigger self-host build - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} script: | From 97f025c3435318999cb962e2e9ce2c75b5e9f092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= <dani-garcia@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:57:47 +0200 Subject: [PATCH 54/83] [PM-2021] Remove startup entry on windows uninstall (#16701) --- apps/desktop/installer.nsh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/desktop/installer.nsh b/apps/desktop/installer.nsh index f8939423c8d..3fe8a69b089 100644 --- a/apps/desktop/installer.nsh +++ b/apps/desktop/installer.nsh @@ -7,3 +7,14 @@ ${endif} ${endif} !macroend + +# When the user is uninstalling the app, remove the auto-start registry entries +!macro customUnInstall + ${ifNot} ${isUpdated} + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "electron.app.${PRODUCT_NAME}" + DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\Run" "electron.app.${PRODUCT_NAME}" + + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run" "electron.app.${PRODUCT_NAME}" + DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run" "electron.app.${PRODUCT_NAME}" + ${endIf} +!macroend From 89e866c1874b76e2a3861e76108c8f821aa4a0cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:59:53 +0200 Subject: [PATCH 55/83] [deps] Platform: Update electron to v38 (#16423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [deps] Platform: Update electron to v38 * Update electron builder and version * Update to electron 38.2.0 to fix high CPU usage on tahoe --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com> --- apps/desktop/electron-builder.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 4d0dac1242a..ab9a162f8c5 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -20,7 +20,7 @@ "**/node_modules/@bitwarden/desktop-napi/index.js", "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node" ], - "electronVersion": "36.8.1", + "electronVersion": "38.2.0", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/package-lock.json b/package-lock.json index 52c156d25eb..a955d12294e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -131,7 +131,7 @@ "copy-webpack-plugin": "13.0.0", "cross-env": "10.0.0", "css-loader": "7.1.2", - "electron": "36.8.1", + "electron": "38.2.0", "electron-builder": "26.0.12", "electron-log": "5.4.0", "electron-reload": "2.0.0-alpha.1", @@ -20159,9 +20159,9 @@ } }, "node_modules/electron": { - "version": "36.8.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-36.8.1.tgz", - "integrity": "sha512-honaH58/cyCb9QAzIvD+WXWuNIZ0tW9zfBqMz5wZld/rXB+LCTEDb2B3TAv8+pDmlzPlkPio95RkUe86l6MNjg==", + "version": "38.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-38.2.0.tgz", + "integrity": "sha512-Cw5Mb+N5NxsG0Hc1qr8I65Kt5APRrbgTtEEn3zTod30UNJRnAE1xbGk/1NOaDn3ODzI/MYn6BzT9T9zreP7xWA==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index 127e375b92c..5015f8278b2 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "copy-webpack-plugin": "13.0.0", "cross-env": "10.0.0", "css-loader": "7.1.2", - "electron": "36.8.1", + "electron": "38.2.0", "electron-builder": "26.0.12", "electron-log": "5.4.0", "electron-reload": "2.0.0-alpha.1", From a690e9a62601fb71a37468f65e024a9765d3e6ba Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:07:20 -0700 Subject: [PATCH 56/83] [PM-26406] - [Defect] Cannot import account restricted json (#16709) * cast boolean values in toSdkCipherView * update to toSdkCipherView * fix toSdkCipherView * fix toSdkCipherView --- libs/common/src/vault/models/domain/cipher.ts | 2 +- libs/common/src/vault/models/view/cipher.view.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index b80f38f66af..c4ee35b2b8f 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -349,7 +349,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> { */ toSdkCipher(): SdkCipher { const sdkCipher: SdkCipher = { - id: asUuid(this.id), + id: this.id ? asUuid(this.id) : undefined, organizationId: this.organizationId ? asUuid(this.organizationId) : undefined, folderId: this.folderId ? asUuid(this.folderId) : undefined, collectionIds: this.collectionIds ? this.collectionIds.map(asUuid) : ([] as any), diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index d52e6eb11bd..b9f717b3a7f 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -325,11 +325,11 @@ export class CipherView implements View, InitializerMetadata { name: this.name ?? "", notes: this.notes, type: this.type ?? CipherType.Login, - favorite: this.favorite, - organizationUseTotp: this.organizationUseTotp, + favorite: this.favorite ?? false, + organizationUseTotp: this.organizationUseTotp ?? false, permissions: this.permissions?.toSdkCipherPermissions(), - edit: this.edit, - viewPassword: this.viewPassword, + edit: this.edit ?? true, + viewPassword: this.viewPassword ?? true, localData: toSdkLocalData(this.localData), attachments: this.attachments?.map((a) => a.toSdkAttachmentView()), fields: this.fields?.map((f) => f.toSdkFieldView()), From a3696ea3c1b77e32ad41f2b60bfe25f3dfdf23e0 Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Fri, 3 Oct 2025 12:03:00 -0500 Subject: [PATCH 57/83] PM-26495 Activity tab empty state changed (#16726) --- apps/web/src/locales/en/messages.json | 2 +- .../all-activity.component.html | 14 +------------- .../access-intelligence/all-activity.component.ts | 4 +--- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 1a8623a1973..5ed393c0295 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -93,7 +93,7 @@ "sendReminders": { "message": "Send reminders" }, - "criticalApplicationsActivityDescription": { + "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index bb5416a9a13..a1b5611ff14 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -4,19 +4,7 @@ </div> } -@if (!(isLoading$ | async) && (noData$ | async)) { - <div class="tw-mt-4"> - <bit-no-items class="tw-text-main"> - <ng-container slot="title"> - <h2 class="tw-font-semibold tw-mt-4"> - {{ "noAppsInOrgTitle" | i18n: organization?.name }} - </h2> - </ng-container> - </bit-no-items> - </div> -} - -@if (!(isLoading$ | async) && !(noData$ | async)) { +@if (!(isLoading$ | async)) { <ul class="tw-inline-grid tw-grid-cols-3 tw-gap-6 tw-m-0 tw-p-0 tw-w-full tw-auto-cols-auto tw-list-none" > diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts index f1aa6f1041b..1691e35c819 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts @@ -1,7 +1,7 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; -import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AllActivitiesService, @@ -31,7 +31,6 @@ import { RiskInsightsTabType } from "./risk-insights.component"; }) export class AllActivityComponent implements OnInit { protected isLoading$ = this.dataService.isLoading$; - protected noData$ = new BehaviorSubject(true); organization: Organization | null = null; totalCriticalAppsAtRiskMemberCount = 0; totalCriticalAppsCount = 0; @@ -53,7 +52,6 @@ export class AllActivityComponent implements OnInit { this.allActivitiesService.reportSummary$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((summary) => { - this.noData$.next(summary.totalApplicationCount === 0); this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; From 72f7d8b96fcd1945fcf2c13f4061e9a7400c47de Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:30:12 -0700 Subject: [PATCH 58/83] Revert "group collection in getNestedCollectionTree" (#16731) This reverts commit 1578f44add9e78b342cf8d210b598a0840a067ab. --- .../collections/utils/collection-utils.ts | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts index 91e9fd4cdcc..67cb4c7cdc8 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts @@ -5,7 +5,6 @@ import { CollectionView, NestingDelimiter, } from "@bitwarden/admin-console/common"; -import { OrganizationId } from "@bitwarden/common/types/guid"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; @@ -27,23 +26,15 @@ export function getNestedCollectionTree( .sort((a, b) => a.name.localeCompare(b.name)) .map(cloneCollection); - const all: TreeNode<CollectionView | CollectionAdminView>[] = []; - const groupedByOrg = new Map<OrganizationId, CollectionView[]>(); - clonedCollections.map((c) => { - const key = c.organizationId; - (groupedByOrg.get(key) ?? groupedByOrg.set(key, []).get(key)!).push(c); + const nodes: TreeNode<CollectionView | CollectionAdminView>[] = []; + clonedCollections.forEach((collection) => { + const parts = + collection.name != null + ? collection.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) + : []; + ServiceUtils.nestedTraverse(nodes, 0, parts, collection, null, NestingDelimiter); }); - - for (const group of groupedByOrg.values()) { - const nodes: TreeNode<CollectionView>[] = []; - for (const c of group) { - const collectionCopy = Object.assign(new CollectionView({ ...c, name: c.name }), c); - const parts = c.name ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; - ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, undefined, NestingDelimiter); - } - all.push(...nodes); - } - return all; + return nodes; } export function cloneCollection(collection: CollectionView): CollectionView; From 96841129e6daaf1759758f8f1124d6260dacf95d Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 3 Oct 2025 23:08:23 +0200 Subject: [PATCH 59/83] Use more specific error message when no login to import were found (#16723) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- .../src/components/chrome/import-chrome.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/importer/src/components/chrome/import-chrome.component.ts b/libs/importer/src/components/chrome/import-chrome.component.ts index 9e9f985c5a5..bf2a37203e1 100644 --- a/libs/importer/src/components/chrome/import-chrome.component.ts +++ b/libs/importer/src/components/chrome/import-chrome.component.ts @@ -114,7 +114,11 @@ export class ImportChromeComponent implements OnInit, OnDestroy { this.formGroup.controls.profile.value, ); if (logins.length === 0) { - throw "nothing to import"; + return { + errors: { + message: this.i18nService.t("importNothingError"), + }, + }; } const chromeLogins: ChromeLogin[] = []; for (const l of logins) { From 0daecf4a77ce289eb03d39aebac7d43c7076f050 Mon Sep 17 00:00:00 2001 From: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:50:49 +0200 Subject: [PATCH 60/83] Display inline autofill on Gap.com password field (#16085) --- .../collect-autofill-content.service.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.ts index 2ddee289044..47b1c9ea6df 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -433,7 +433,6 @@ export class CollectAutofillContentService implements CollectAutofillContentServ /** * Caches the autofill field element and its data. - * Will not cache the element if the index is less than 0. * * @param index - The index of the autofill field element * @param element - The autofill field element to cache @@ -444,10 +443,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ element: ElementWithOpId<FormFieldElement>, autofillFieldData: AutofillField, ) { - if (index < 0) { - return; - } - + // Always cache the element, even if index is -1 (for dynamically added fields) this.autofillFieldElements.set(element, autofillFieldData); } @@ -1196,7 +1192,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ private setupOverlayListenersOnMutatedElements(mutatedElements: Node[]) { for (let elementIndex = 0; elementIndex < mutatedElements.length; elementIndex++) { const node = mutatedElements[elementIndex]; - const buildAutofillFieldItem = () => { + const buildAutofillFieldItem = async () => { if ( !this.isNodeFormFieldElement(node) || this.autofillFieldElements.get(node as ElementWithOpId<FormFieldElement>) @@ -1206,7 +1202,17 @@ export class CollectAutofillContentService implements CollectAutofillContentServ // We are setting this item to a -1 index because we do not know its position in the DOM. // This value should be updated with the next call to collect page details. - void this.buildAutofillFieldItem(node as ElementWithOpId<FormFieldElement>, -1); + const formFieldElement = node as ElementWithOpId<FormFieldElement>; + const autofillField = await this.buildAutofillFieldItem(formFieldElement, -1); + + // Set up overlay listeners for the new field if we have the overlay service + if (autofillField && this.autofillOverlayContentService) { + this.setupOverlayOnField(formFieldElement, autofillField); + + if (this.domRecentlyMutated) { + this.updateAutofillElementsAfterMutation(); + } + } }; requestIdleCallbackPolyfill(buildAutofillFieldItem, { timeout: 1000 }); From f41869cbbaf90bec215ac904f2303800fd0cbd22 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 07:55:41 +0000 Subject: [PATCH 61/83] Autosync the updated translations (#16742) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/bg/messages.json | 2 +- apps/browser/src/_locales/cs/messages.json | 2 +- apps/browser/src/_locales/de/messages.json | 40 +++++++++---------- apps/browser/src/_locales/en_IN/messages.json | 4 +- apps/browser/src/_locales/hr/messages.json | 10 ++--- apps/browser/src/_locales/hu/messages.json | 2 +- apps/browser/src/_locales/lv/messages.json | 2 +- apps/browser/src/_locales/nl/messages.json | 2 +- apps/browser/src/_locales/pt_PT/messages.json | 6 +-- apps/browser/src/_locales/ru/messages.json | 2 +- apps/browser/src/_locales/sk/messages.json | 2 +- apps/browser/src/_locales/sr/messages.json | 10 ++--- apps/browser/src/_locales/sv/messages.json | 2 +- apps/browser/src/_locales/tr/messages.json | 2 +- apps/browser/src/_locales/vi/messages.json | 6 +-- apps/browser/src/_locales/zh_CN/messages.json | 10 ++--- 16 files changed, 52 insertions(+), 52 deletions(-) diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 5d6b1b38380..d27e426d4aa 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -734,7 +734,7 @@ "message": "Грешна главна парола" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Грешна главна парола. Проверете дали е-пощата е правилна и дали акаунтът Ви е създаден в $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index eaf86015839..8820af69f98 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -734,7 +734,7 @@ "message": "Chybné hlavní heslo" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Neplatné hlavní heslo. Potvrďte správnost e-mailu a zda byl Váš účet vytvořen na $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index bc86e48476a..ce26078d427 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -559,13 +559,13 @@ "description": "Verb" }, "unarchive": { - "message": "Archivierung aufheben" + "message": "Nicht mehr archivieren" }, "itemsInArchive": { "message": "Einträge im Archiv" }, "noItemsInArchive": { - "message": "Kein Eintrag im Archiv" + "message": "Keine Einträge im Archiv" }, "noItemsInArchiveDesc": { "message": "Archivierte Einträge werden hier angezeigt und von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen." @@ -589,7 +589,7 @@ "message": "Anzeigen" }, "viewLogin": { - "message": "Login ansehen" + "message": "Zugangsdaten anzeigen" }, "noItemsInList": { "message": "Keine Einträge zum Anzeigen vorhanden." @@ -734,7 +734,7 @@ "message": "Ungültiges Master-Passwort" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ungültiges Master-Passwort. Überprüfe, ob deine E-Mail-Adresse korrekt ist und dein Konto auf $HOST$ erstellt wurde.", "placeholders": { "host": { "content": "$1", @@ -1656,7 +1656,7 @@ "message": "Deaktiviere die Auto-Ausfüllen-Einstellungen deines Browsers, damit sie nicht mit Bitwarden in Konflikt geraten." }, "turnOffBrowserAutofill": { - "message": "Deaktiviere das automatische Ausfüllen von $BROWSER$", + "message": "$BROWSER$ Auto-Ausfüllen deaktivieren", "placeholders": { "browser": { "content": "$1", @@ -1665,7 +1665,7 @@ } }, "turnOffAutofill": { - "message": "Deaktiviere das automatische Ausfüllen" + "message": "Auto-Ausfüllen deaktivieren" }, "showInlineMenuLabel": { "message": "Vorschläge zum Auto-Ausfüllen in Formularfeldern anzeigen" @@ -1796,7 +1796,7 @@ "message": "Dieses Pop-up Fenster wird geschlossen, wenn du außerhalb des Fensters klickst um in deinen E-Mails nach dem Verifizierungscode zu suchen. Möchtest du, dass dieses Pop-up in einem separaten Fenster geöffnet wird, damit es nicht geschlossen wird?" }, "showIconsChangePasswordUrls": { - "message": "Website-Symbole anzeigen und URLs zum Ändern von Passwörtern abrufen" + "message": "Website-Symbole anzeigen und URLs zum Ändern von Passwörtern ermitteln" }, "cardholderName": { "message": "Name des Karteninhabers" @@ -3570,7 +3570,7 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Du hast einen Zugangsdaten-Versuch von einem anderen Gerät abgelehnt. Falls du es warst, versuche dich erneut mit diesem Gerät anzumelden." + "message": "Du hast einen Anmeldeversuch von einem anderen Gerät abgelehnt. Wenn du das wirklich warst, versuche dich erneut mit dem Gerät anzumelden." }, "device": { "message": "Gerät" @@ -3860,7 +3860,7 @@ "message": "Organisation ist nicht vertrauenswürdig" }, "emergencyAccessTrustWarning": { - "message": "Zur Sicherheit deines Kontos bestätige nur, wenn du diesem Benutzer Notfallzugriff gewährt hast und sein Fingerabdruck mit dem übereinstimmt, was in seinem Konto angezeigt wird" + "message": "Bestätige zur Sicherheit deines Kontos nur, wenn du den Notfallzugriff diesem Benutzer gewährt hast und sein Fingerabdruck mit dem übereinstimmt, was in seinem Konto angezeigt wird" }, "orgTrustWarning": { "message": "Fahre zur Sicherheit deines Kontos nur fort, wenn du ein Mitglied dieser Organisation bist, die Kontowiederherstellung aktiviert hast und der unten angezeigte Fingerabdruck mit dem Fingerabdruck der Organisation übereinstimmt." @@ -4211,10 +4211,10 @@ "message": "Sammlung auswählen" }, "importTargetHintCollection": { - "message": "Wählen Sie diese Option, wenn der importierte Dateiinhalt in eine Sammlung verschoben werden soll" + "message": "Wähle diese Option, wenn der importierte Dateiinhalt in eine Sammlung verschoben werden soll" }, "importTargetHintFolder": { - "message": "Wählen Sie diese Option, wenn der importierte Dateiinhalt in einen Ordner verschoben werden soll" + "message": "Wähle diese Option, wenn der importierte Dateiinhalt in einen Ordner verschoben werden soll" }, "importUnassignedItemsError": { "message": "Die Datei enthält nicht zugewiesene Einträge." @@ -5517,10 +5517,10 @@ "message": "Gefährdetes Passwort ändern" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "Dieser Login ist gefährdet und es fehlt eine Website. Fügen Sie eine Website hinzu und ändern Sie das Passwort, um die Sicherheit zu erhöhen." + "message": "Diese Zugangsdaten sind gefährdet und es fehlt eine Website. Füge eine Website hinzu und ändere das Passwort für mehr Sicherheit." }, "missingWebsite": { - "message": "Fehlende Webseite" + "message": "Fehlende Website" }, "settingsVaultOptions": { "message": "Tresoroptionen" @@ -5568,10 +5568,10 @@ "message": "Jetzt importieren" }, "hasItemsVaultNudgeTitle": { - "message": "Willkommen in Ihrem Tresor!" + "message": "Willkommen in deinem Tresor!" }, "phishingPageTitle": { - "message": "Phishing Webseite" + "message": "Phishing-Website" }, "phishingPageCloseTab": { "message": "Tab schließen" @@ -5580,7 +5580,7 @@ "message": "Weiter" }, "phishingPageLearnWhy": { - "message": "Warum sehen Sie das?" + "message": "Warum wird dir das angezeigt?" }, "hasItemsVaultNudgeBodyOne": { "message": "Einträge für die aktuelle Seite automatisch ausfüllen" @@ -5605,7 +5605,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "damit diese Zugangsdaten als Vorschlag zum automatischen Ausfüllen erscheinen.", + "message": "hinzu, damit diese Zugangsdaten als Auto-Ausfüllen-Vorschlag erscheinen.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5667,7 +5667,7 @@ "message": "Du hast keine Berechtigung, diese Seite anzuzeigen. Versuche, dich mit einem anderen Konto anzumelden." }, "wasmNotSupported": { - "message": "WebAssembly wird in Ihrem Browser nicht unterstützt oder ist nicht aktiviert. WebAssembly ist erforderlich, um die Bitwarden-App zu verwenden.", + "message": "WebAssembly wird von deinem Browser nicht unterstützt oder ist nicht aktiviert. WebAssembly wird benötigt, um die Bitwarden-App nutzen zu können.", "description": "'WebAssembly' is a technical term and should not be translated." }, "showMore": { @@ -5680,10 +5680,10 @@ "message": "Weiter" }, "moreBreadcrumbs": { - "message": "Mehr Breadcrumbs", + "message": "Dateipfad erweitern", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." }, "confirmKeyConnectorDomain": { - "message": "Bestätige Key Connector Domäne" + "message": "Key Connector-Domain bestätigen" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index bf8e62e4f83..cf8b7e08d22 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -3208,7 +3208,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported.", "placeholders": { "organization": { "content": "$1", @@ -3217,7 +3217,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 7d83ecdab1d..34c114c214d 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -551,11 +551,11 @@ "message": "Ponovno postavljanje pretraživanja" }, "archiveNoun": { - "message": "Archive", + "message": "Arhiva", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arhiviraj", "description": "Verb" }, "unarchive": { @@ -734,7 +734,7 @@ "message": "Neispravna glavna lozinka" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Nevažeća glavna lozinka. Provjeri je li tvoja adresa e-pošta ispravna i je li račun kreiran na $HOST$.", "placeholders": { "host": { "content": "$1", @@ -3208,7 +3208,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Izvezt će se samo trezor organizacije povezan s $ORGANIZATION$.", "placeholders": { "organization": { "content": "$1", @@ -3217,7 +3217,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Izvezt će se samo trezor organizacije povezan s $ORGANIZATION$. Zbirka mojih stavki neće biti uključena.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index def94f2f283..2cafe38fab4 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -734,7 +734,7 @@ "message": "Hibás mesterjelszó" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "A mesterjelszó érvénytelen. Erősítsük meg, hogy email cím helyes és a fiók létrehozásának helye: $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 3cf4fef05e5..f9a8c3fba2b 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -734,7 +734,7 @@ "message": "Nederīga galvenā parole" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Nederīga galvenā parole. Jāpārliecinās, ka e-pasta adrese ir pareiza un konts tika izveidots $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 521d9788e0e..62f81cf0897 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -734,7 +734,7 @@ "message": "Ongeldig hoofdwachtwoord" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ongeldig hoofdwachtwoord. Check of je e-mailadres klopt en of je account is aangemaakt op $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 6782468d4b0..345be1766de 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -734,7 +734,7 @@ "message": "Palavra-passe mestra inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Palavra-passe mestra inválida. Confirme se o seu e-mail está correto e se a sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -3208,7 +3208,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado.", "placeholders": { "organization": { "content": "$1", @@ -3217,7 +3217,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. As coleções dos meus itens não serão incluídas.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index d4c83aa7ccf..593e2b5d672 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -734,7 +734,7 @@ "message": "Неверный мастер-пароль" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Неверный мастер-пароль. Подтвердите, что ваш адрес email указан верно и ваш аккаунт был создан на $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index c6e79e241aa..cfc1404e9bf 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -734,7 +734,7 @@ "message": "Neplatné hlavné heslo" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Neplatné hlavné heslo. Potvrďte, že váš e-mail je správny a účet bol vytvorený na $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index a32d2c33504..a915463799a 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -551,11 +551,11 @@ "message": "Ресетовати претрагу" }, "archiveNoun": { - "message": "Archive", + "message": "Архива", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архива", "description": "Verb" }, "unarchive": { @@ -734,7 +734,7 @@ "message": "Погрешна главна лозинка" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Неважећа главна лозинка. Потврдите да је ваш имејл тачан и ваш рачун је креиран на $HOST$.", "placeholders": { "host": { "content": "$1", @@ -3208,7 +3208,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Извешће се само сеф организације повезана са $ORGANIZATION$.", "placeholders": { "organization": { "content": "$1", @@ -3217,7 +3217,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Извешће се само сеф организације повезан са $ORGANIZATION$. Колекције мојих предмета неће бити укључене.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index a84efb53464..fb83fdf22b6 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -734,7 +734,7 @@ "message": "Ogiltigt huvudlösenord" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ogiltigt huvudlösenord. Bekräfta att din e-postadress är korrekt och ditt konto skapades på $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 040c92f01c4..470589c286d 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -734,7 +734,7 @@ "message": "Geçersiz ana parola" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ana parola geçersiz. E-posta adresinizin doğru olduğunu ve hesabınızın $HOST$ üzerinde oluşturulduğunu kontrol edin.", "placeholders": { "host": { "content": "$1", diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 7b74d5c60b8..8b69fdc2512 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -734,7 +734,7 @@ "message": "Mật khẩu chính không hợp lệ" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Mật khẩu chính không hợp lệ. Xác nhận email của bạn là chính xác và tài khoản được tạo trên $HOST$.", "placeholders": { "host": { "content": "$1", @@ -3208,7 +3208,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Chỉ kho lưu trữ tổ chức liên kết với $ORGANIZATION$ sẽ được xuất.", "placeholders": { "organization": { "content": "$1", @@ -3217,7 +3217,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Chỉ kho lưu trữ tổ chức liên kết với $ORGANIZATION$ được xuất. Bộ sưu tập mục của tôi sẽ không được bao gồm.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 2b7a34aa1a2..9ced7b1efd7 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -551,11 +551,11 @@ "message": "重置搜索" }, "archiveNoun": { - "message": "Archive", + "message": "归档", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "归档", "description": "Verb" }, "unarchive": { @@ -734,7 +734,7 @@ "message": "无效的主密码" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "无效的主密码。请确认您的电子邮箱正确无误,以及您的账户是在 $HOST$ 上创建的。", "placeholders": { "host": { "content": "$1", @@ -3208,7 +3208,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库。", "placeholders": { "organization": { "content": "$1", @@ -3217,7 +3217,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库,不包括我的项目集合。", "placeholders": { "organization": { "content": "$1", From 125bfd587a6ecabcc211f57a62577848095805b7 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 07:55:59 +0000 Subject: [PATCH 62/83] Autosync the updated translations (#16741) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/bg/messages.json | 4 +-- apps/desktop/src/locales/ca/messages.json | 8 ++--- apps/desktop/src/locales/cs/messages.json | 2 +- apps/desktop/src/locales/de/messages.json | 32 ++++++++++---------- apps/desktop/src/locales/en_GB/messages.json | 4 +-- apps/desktop/src/locales/en_IN/messages.json | 4 +-- apps/desktop/src/locales/hr/messages.json | 18 +++++------ apps/desktop/src/locales/hu/messages.json | 2 +- apps/desktop/src/locales/lv/messages.json | 2 +- apps/desktop/src/locales/nl/messages.json | 2 +- apps/desktop/src/locales/pt_PT/messages.json | 6 ++-- apps/desktop/src/locales/ru/messages.json | 2 +- apps/desktop/src/locales/sk/messages.json | 2 +- apps/desktop/src/locales/sr/messages.json | 18 +++++------ apps/desktop/src/locales/sv/messages.json | 2 +- apps/desktop/src/locales/tr/messages.json | 2 +- apps/desktop/src/locales/vi/messages.json | 6 ++-- apps/desktop/src/locales/zh_CN/messages.json | 10 +++--- 18 files changed, 63 insertions(+), 63 deletions(-) diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index bb0ad98f55a..091d91dc7d4 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1223,7 +1223,7 @@ "message": "Грешна главна парола" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Грешна главна парола. Проверете дали е-пощата е правилна и дали акаунтът Ви е създаден в $HOST$.", "placeholders": { "host": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Само трезора свързан с $ORGANIZATION$ ще бъде експортиран. Моите записи няма да бъдат включени.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 9bf578efb9f..3f21a9d969d 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -3018,7 +3018,7 @@ "message": "aplicació web" }, "notificationSentDevicePart2": { - "message": "Assegura't que la frase d'empremta digital encaixa amb la d'aquí sota abans d'aprovar-la." + "message": "Assegureu-vos que la frase de l'empremta digital coincideix amb la que hi ha a continuació abans d'aprovar-la." }, "needAnotherOptionV1": { "message": "Necessiteu una altra opció?" @@ -3135,7 +3135,7 @@ "message": "Aquesta sol·licitud ja no és vàlida." }, "confirmAccessAttempt": { - "message": "Confirma l'intent d'accés de $EMAIL$", + "message": "Confirmeu l'intent d'accés de $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3147,7 +3147,7 @@ "message": "S'ha sol·licitat inici de sessió" }, "accountAccessRequested": { - "message": "Accés al compte demanat" + "message": "S'ha sol·licitat accés al compte" }, "creatingAccountOn": { "message": "Creant compte en" @@ -3798,7 +3798,7 @@ "message": "Denega" }, "sshkeyApprovalTitle": { - "message": "Confirma l'ús de la clau SSH" + "message": "Confirmeu l'ús de la clau SSH" }, "agentForwardingWarningTitle": { "message": "Advertència: Reenviament de l'Agent" diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index b056641ce64..cd77dcd1d06 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1223,7 +1223,7 @@ "message": "Chybné hlavní heslo" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Neplatné hlavní heslo. Potvrďte správnost e-mailu a zda byl Váš účet vytvořen na $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index eb3b713bb3e..56f45e04c08 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -415,7 +415,7 @@ "message": "Authenticator-Schlüssel" }, "autofillOptions": { - "message": "Optionen für automatisches Ausfüllen" + "message": "Auto-Ausfüllen-Optionen" }, "websiteUri": { "message": "Website (URI)" @@ -449,7 +449,7 @@ "message": "Feld bearbeiten" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Sind Sie sicher, dass Sie diesen Anhang dauerhaft löschen möchten?" + "message": "Bist du sicher, dass du diesen Anhang dauerhaft löschen möchtest?" }, "fieldType": { "message": "Feldtyp" @@ -1223,7 +1223,7 @@ "message": "Ungültiges Master-Passwort" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ungültiges Master-Passwort. Überprüfe, ob deine E-Mail-Adresse korrekt ist und dein Konto auf $HOST$ erstellt wurde.", "placeholders": { "host": { "content": "$1", @@ -3513,10 +3513,10 @@ "message": "Sammlung auswählen" }, "importTargetHintCollection": { - "message": "Wählen Sie diese Option, wenn die Inhalte der Import-Datei in eine Sammlung verschoben werden sollen" + "message": "Wähle diese Option, wenn der importierte Dateiinhalt in eine Sammlung verschoben werden soll" }, "importTargetHintFolder": { - "message": "Wählen Sie diese Option, wenn die Inhalte der Import-Datei in einen Ordner verschoben werden sollen" + "message": "Wähle diese Option, wenn der importierte Dateiinhalt in einen Ordner verschoben werden soll" }, "importUnassignedItemsError": { "message": "Die Datei enthält nicht zugewiesene Einträge." @@ -3613,7 +3613,7 @@ "message": "Bitte melde dich weiterhin mit deinen Firmenzugangsdaten an." }, "importDirectlyFromBrowser": { - "message": "Direkter Import aus dem Browser" + "message": "Direkt aus dem Browser importieren" }, "browserProfile": { "message": "Browser-Profil" @@ -3861,10 +3861,10 @@ "message": "Gefährdetes Passwort ändern" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "Diese Zugangsdaten sind gefährdet und enthalten keine Website. Fügen Sie eine Website hinzu und ändern Sie das Passwort für mehr Sicherheit." + "message": "Diese Zugangsdaten sind gefährdet und es fehlt eine Website. Füge eine Website hinzu und ändere das Passwort für mehr Sicherheit." }, "missingWebsite": { - "message": "Fehlende Webseite" + "message": "Fehlende Website" }, "cannotRemoveViewOnlyCollections": { "message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$", @@ -3965,7 +3965,7 @@ "message": "Über diese Einstellung" }, "permitCipherDetailsDescription": { - "message": "Bitwarden verwendet gespeicherte Zugangsdaten-URIs, um zu bestimmen, welches Symbol oder welche Passwort-Ändern-URL verwendet werden soll, um Ihr Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn Sie diesen Dienst nutzen." + "message": "Bitwarden verwendet gespeicherte URIs für die Anmeldung, um zu bestimmen, welches Symbol oder welche URL zum Ändern des Passworts verwendet werden soll, um dein Erlebnis zu verbessern. Es werden keine Informationen erfasst oder gespeichert, wenn du diesen Dienst nutzt." }, "assignToCollections": { "message": "Sammlungen zuweisen" @@ -4111,13 +4111,13 @@ "message": "Bitwarden überprüft die Eingabestellen nicht. Vergewissere dich, dass du dich im richtigen Fenster und Feld befindest, bevor du die Tastenkombination verwendest." }, "typeShortcut": { - "message": "Typ-Verknüpfung" + "message": "Autotype-Tastaturkürzel" }, "editAutotypeShortcutDescription": { - "message": "Fügen Sie einen oder zwei der folgenden Modifikatoren ein: Strg, Alt, Win oder Umschalt, sowie einen Buchstaben." + "message": "Füge einen oder zwei der folgenden Modifikatoren ein: Strg, Alt, Win oder Umschalttaste, sowie einen Buchstaben." }, "invalidShortcut": { - "message": "Ungültige Verknüpfung" + "message": "Ungültiges Tastaturkürzel" }, "moreBreadcrumbs": { "message": "Weitere Navigationspfade", @@ -4133,13 +4133,13 @@ "message": "Bestätigen" }, "enableAutotypeShortcutPreview": { - "message": "Autotype-Shortcut aktivieren (Funktionsvorschau)" + "message": "Autotype-Tastaturkürzel aktivieren (Funktionsvorschau)" }, "enableAutotypeDescriptionTransitionKey": { - "message": "Stellen Sie sicher, dass Sie sich im richtigen Feld befinden, bevor Sie die Verknüpfung verwenden, um zu vermeiden, dass Daten an der falschen Stelle ausgefüllt werden." + "message": "Stell sicher, dass du dich im richtigen Feld befindest, bevor du das Tastaturkürzel benutzt, um zu vermeiden, dass Daten an der falschen Stelle ausgefüllt werden." }, "editShortcut": { - "message": "Verknüpfung bearbeiten" + "message": "Tastaturkürzel bearbeiten" }, "archiveNoun": { "message": "Archiv", @@ -4171,6 +4171,6 @@ "message": "Eintrag archivieren" }, "archiveItemConfirmDesc": { - "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen sowie Vorschlägen zum automatischen Ausfüllen ausgeschlossen. Bist du sicher, dass du diesen Eintrag archivieren möchtest?" + "message": "Archivierte Einträge werden von allgemeinen Suchergebnissen und Auto-Ausfüllen-Vorschlägen ausgeschlossen. Bist du sicher, dass du diesen Eintrag archivieren möchtest?" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 5d80bb6e37e..22e2c375561 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2664,7 +2664,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported.", "placeholders": { "organization": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 723af507de2..269feba18d5 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2664,7 +2664,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported.", "placeholders": { "organization": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 2f49ac1eb6d..11ab8b7d223 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1223,7 +1223,7 @@ "message": "Neispravna glavna lozinka" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Nevažeća glavna lozinka. Provjeri je li tvoja adresa e-pošta ispravna i je li račun kreiran na $HOST$.", "placeholders": { "host": { "content": "$1", @@ -2664,7 +2664,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Izvezt će se samo trezor organizacije povezan s $ORGANIZATION$.", "placeholders": { "organization": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Izvezt će se samo trezor organizacije povezan s $ORGANIZATION$. Zbirka mojih stavki neće biti uključena.", "placeholders": { "organization": { "content": "$1", @@ -4111,13 +4111,13 @@ "message": "Bitwarden ne provjerava lokacije unosa, prije korištenja prečaca provjeri da si u pravom prozoru i polju." }, "typeShortcut": { - "message": "Type shortcut" + "message": "Vrsta prečaca" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "Uključi jedan ili dva modifikatora: Ctrl, Alt, Win ili Shift i slovo." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "Nevažeći prečac" }, "moreBreadcrumbs": { "message": "Više mrvica", @@ -4133,7 +4133,7 @@ "message": "Potvrdi" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "Uključi prečac Autotype (Pretpregled značajke)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Prije korištenja prečaca provjeri nalaziš li se u ispravnom polju kako se podaci ne bi unijeli na pogrešno mjesto." @@ -4142,11 +4142,11 @@ "message": "Uredi prečac" }, "archiveNoun": { - "message": "Archive", + "message": "Arhiva", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arhiviraj", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index ba1e9191063..393340a6dfb 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1223,7 +1223,7 @@ "message": "A mesterjelszó érvénytelen." }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "A mesterjelszó érvénytelen. Erősítsük meg, hogy email cím helyes és a fiók létrehozásának helye: $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index d8a37b5d79c..4ad26552858 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1223,7 +1223,7 @@ "message": "Nederīga galvenā parole" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Nederīga galvenā parole. Jāpārliecinās, ka e-pasta adrese ir pareiza un konts tika izveidots $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 36d6c661a78..a3896955162 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1223,7 +1223,7 @@ "message": "Ongeldig hoofdwachtwoord" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ongeldig hoofdwachtwoord. Check of je e-mailadres klopt en of je account is aangemaakt op $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 71030592b4c..74c4e3077d2 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1223,7 +1223,7 @@ "message": "Palavra-passe mestra inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Palavra-passe mestra inválida. Confirme se o seu e-mail está correto e se a sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -2664,7 +2664,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado.", "placeholders": { "organization": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. As coleções dos meus itens não serão incluídas.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index f933557d9c9..c1c2cee8bf5 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1223,7 +1223,7 @@ "message": "Неверный мастер-пароль" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Неверный мастер-пароль. Подтвердите, что ваш адрес email указан верно и ваш аккаунт был создан на $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 17713ac4655..491d91913f9 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1223,7 +1223,7 @@ "message": "Neplatné hlavné heslo" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Neplatné hlavné heslo. Potvrďte, že váš e-mail je správny a účet bol vytvorený na $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 87c52401aa9..8e68918ff5b 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1223,7 +1223,7 @@ "message": "Погрешна главна лозинка" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Неисправна главна лозинка. Потврдите да је адреса ваше е-поште исправна и да је ваш налог направљен на $HOST$.", "placeholders": { "host": { "content": "$1", @@ -2664,7 +2664,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Извешће се само сеф организације повезана са $ORGANIZATION$.", "placeholders": { "organization": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Извешће се само сеф организације повезан са $ORGANIZATION$. Колекције мојих предмета неће бити укључене.", "placeholders": { "organization": { "content": "$1", @@ -4111,13 +4111,13 @@ "message": "Bitwarden не потврђује локације уноса, будите сигурни да сте у добром прозору и поље пре употребе пречице." }, "typeShortcut": { - "message": "Type shortcut" + "message": "Унети пречицу" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "Укључите један или два следећа модификатора: Ctrl, Alt, Win, или Shift, и слово." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "Неважећа пречица" }, "moreBreadcrumbs": { "message": "Више мрвица", @@ -4133,7 +4133,7 @@ "message": "Потврди" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "Омогућите пречицу ауто-уноса (преглед функције)" }, "enableAutotypeDescriptionTransitionKey": { "message": "Будите сигурни да сте у исправном пољу пре употребе пречице да бисте избегли попуњавање података на погрешно место." @@ -4142,11 +4142,11 @@ "message": "Уреди пречицу" }, "archiveNoun": { - "message": "Archive", + "message": "Архива", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архивирај", "description": "Verb" }, "unarchive": { diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index a605e15badb..45caa056055 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1223,7 +1223,7 @@ "message": "Ogiltigt huvudlösenord" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ogiltigt huvudlösenord. Bekräfta att din e-postadress är korrekt och ditt konto skapades på $HOST$.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 25ac25758c6..542e5b07ace 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1223,7 +1223,7 @@ "message": "Geçersiz ana parola" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ana parola geçersiz. E-posta adresinizin doğru olduğunu ve hesabınızın $HOST$ üzerinde oluşturulduğunu kontrol edin.", "placeholders": { "host": { "content": "$1", diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index d027c9d0f56..c044d36b47f 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1223,7 +1223,7 @@ "message": "Mật khẩu chính không hợp lệ" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Mật khẩu chính không hợp lệ. Xác nhận email của bạn là chính xác và tài khoản được tạo trên $HOST$.", "placeholders": { "host": { "content": "$1", @@ -2664,7 +2664,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Chỉ kho lưu trữ tổ chức liên kết với $ORGANIZATION$ sẽ được xuất.", "placeholders": { "organization": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Chỉ kho lưu trữ tổ chức liên kết với $ORGANIZATION$ được xuất. Bộ sưu tập mục của tôi sẽ không được bao gồm.", "placeholders": { "organization": { "content": "$1", diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 8e2dfd67acd..974e3d39101 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1223,7 +1223,7 @@ "message": "无效的主密码" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "无效的主密码。请确认您的电子邮箱正确无误,以及您的账户是在 $HOST$ 上创建的。", "placeholders": { "host": { "content": "$1", @@ -2664,7 +2664,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库。", "placeholders": { "organization": { "content": "$1", @@ -2673,7 +2673,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库,不包括我的项目集合。", "placeholders": { "organization": { "content": "$1", @@ -4142,11 +4142,11 @@ "message": "编辑快捷键" }, "archiveNoun": { - "message": "Archive", + "message": "归档", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "归档", "description": "Verb" }, "unarchive": { From 05ea360675b6b402baab0fa308484e8009588454 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:10:21 +0000 Subject: [PATCH 63/83] Autosync the updated translations (#16743) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 138 +++++++++++++- apps/web/src/locales/ar/messages.json | 138 +++++++++++++- apps/web/src/locales/az/messages.json | 138 +++++++++++++- apps/web/src/locales/be/messages.json | 138 +++++++++++++- apps/web/src/locales/bg/messages.json | 138 +++++++++++++- apps/web/src/locales/bn/messages.json | 138 +++++++++++++- apps/web/src/locales/bs/messages.json | 138 +++++++++++++- apps/web/src/locales/ca/messages.json | 138 +++++++++++++- apps/web/src/locales/cs/messages.json | 138 +++++++++++++- apps/web/src/locales/cy/messages.json | 138 +++++++++++++- apps/web/src/locales/da/messages.json | 138 +++++++++++++- apps/web/src/locales/de/messages.json | 164 ++++++++++++++-- apps/web/src/locales/el/messages.json | 138 +++++++++++++- apps/web/src/locales/en_GB/messages.json | 142 +++++++++++++- apps/web/src/locales/en_IN/messages.json | 142 +++++++++++++- apps/web/src/locales/eo/messages.json | 138 +++++++++++++- apps/web/src/locales/es/messages.json | 138 +++++++++++++- apps/web/src/locales/et/messages.json | 138 +++++++++++++- apps/web/src/locales/eu/messages.json | 138 +++++++++++++- apps/web/src/locales/fa/messages.json | 138 +++++++++++++- apps/web/src/locales/fi/messages.json | 138 +++++++++++++- apps/web/src/locales/fil/messages.json | 138 +++++++++++++- apps/web/src/locales/fr/messages.json | 138 +++++++++++++- apps/web/src/locales/gl/messages.json | 138 +++++++++++++- apps/web/src/locales/he/messages.json | 138 +++++++++++++- apps/web/src/locales/hi/messages.json | 138 +++++++++++++- apps/web/src/locales/hr/messages.json | 176 ++++++++++++++--- apps/web/src/locales/hu/messages.json | 138 +++++++++++++- apps/web/src/locales/id/messages.json | 138 +++++++++++++- apps/web/src/locales/it/messages.json | 138 +++++++++++++- apps/web/src/locales/ja/messages.json | 138 +++++++++++++- apps/web/src/locales/ka/messages.json | 138 +++++++++++++- apps/web/src/locales/km/messages.json | 138 +++++++++++++- apps/web/src/locales/kn/messages.json | 138 +++++++++++++- apps/web/src/locales/ko/messages.json | 138 +++++++++++++- apps/web/src/locales/lv/messages.json | 140 +++++++++++++- apps/web/src/locales/ml/messages.json | 138 +++++++++++++- apps/web/src/locales/mr/messages.json | 138 +++++++++++++- apps/web/src/locales/my/messages.json | 138 +++++++++++++- apps/web/src/locales/nb/messages.json | 138 +++++++++++++- apps/web/src/locales/ne/messages.json | 138 +++++++++++++- apps/web/src/locales/nl/messages.json | 140 +++++++++++++- apps/web/src/locales/nn/messages.json | 138 +++++++++++++- apps/web/src/locales/or/messages.json | 138 +++++++++++++- apps/web/src/locales/pl/messages.json | 138 +++++++++++++- apps/web/src/locales/pt_BR/messages.json | 138 +++++++++++++- apps/web/src/locales/pt_PT/messages.json | 144 +++++++++++++- apps/web/src/locales/ro/messages.json | 138 +++++++++++++- apps/web/src/locales/ru/messages.json | 140 +++++++++++++- apps/web/src/locales/si/messages.json | 138 +++++++++++++- apps/web/src/locales/sk/messages.json | 160 ++++++++++++++-- apps/web/src/locales/sl/messages.json | 138 +++++++++++++- apps/web/src/locales/sr_CS/messages.json | 138 +++++++++++++- apps/web/src/locales/sr_CY/messages.json | 228 ++++++++++++++++++----- apps/web/src/locales/sv/messages.json | 176 ++++++++++++++--- apps/web/src/locales/ta/messages.json | 138 +++++++++++++- apps/web/src/locales/te/messages.json | 138 +++++++++++++- apps/web/src/locales/th/messages.json | 138 +++++++++++++- apps/web/src/locales/tr/messages.json | 140 +++++++++++++- apps/web/src/locales/uk/messages.json | 138 +++++++++++++- apps/web/src/locales/vi/messages.json | 188 ++++++++++++++++--- apps/web/src/locales/zh_CN/messages.json | 174 ++++++++++++++--- apps/web/src/locales/zh_TW/messages.json | 138 +++++++++++++- 63 files changed, 8540 insertions(+), 476 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 0cfad02f7a1..11195502289 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index b9c581f98e2..7dfe8fe7cee 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "إنشاء عنصر تسجيل دخول جديد" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "الأعضاء المبلّغون ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "الأعضاء المعرضون للخطر" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 716fdcb4ea9..e4a4149d9d6 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Yeni giriş elementi yarat" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Məlumatlandırılan üzvlər ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Riskli üzvlər" }, - "membersAtRiskActivityDescription": { - "message": "Kritik tətbiqlər üçün risk altındakı elementlərə düzəliş erişimi olan üzvlər" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Bilinməyən sirr, bu sirrə erişmək üçün icazə istəməli ola bilərsiniz." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Bilinməyən layihə, bu layihəyə erişmək üçün icazə istəməli ola bilərsiniz." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "$PROJECT_ID$ kimliyinə sahib bir layihəyə erişildi.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "$SECRET_ID$ identifikatoruna sahib bir layihəyə düzəliş edildi.", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "$PROJECT_ID$ identifikatoruna sahib bir layihə silindi.", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Təyin et" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Kolleksiyalara təyin et" }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 1a4ca699212..8c6000c3f38 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Стварыць новы элемент запісу ўваходу" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Апавешчаныя ўдзельнікі ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Удзельнікі ў зоне рызыкі" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index a7d6043e253..dd0c14b8f76 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Създаване на нов елемент за вписване" }, + "percentageCompleted": { + "message": "$PERCENT$% завършено", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ от $TOTAL$ задачи по сигурността за завършени", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Напредък по смяната на паролата" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Назначете задачи на членовете, за да следите напредъка" + }, + "onceYouReviewApplications": { + "message": "Когато прегледате приложенията и ги отбележите като важни, те ще се появят тук." + }, + "sendReminders": { + "message": "Изпращане на напомняния" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Когато отбележите някое приложение като важно, то ще се появи тук" + "message": "Когато отбележите някое приложение като важно, то ще се появи тук." }, "viewAtRiskMembers": { "message": "Преглед на членовете в риск" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ пароли в риск", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Известени членове ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Членове в риск" }, - "membersAtRiskActivityDescription": { - "message": "Членове с права за редактиране на елементи в риск за важни приложения" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Членове с достъп до елементи в риск за важни приложения" }, "membersAtRiskCount": { "message": "$COUNT$ членове в риск", @@ -1500,7 +1543,7 @@ "message": "Грешна главна парола" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Грешна главна парола. Проверете дали е-пощата е правилна и дали акаунтът Ви е създаден в $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Неизвестна тайна. Може да се нуждаете от правомощие за достъп до тази тайна." }, + "unknownServiceAccount": { + "message": "Неизвестен машинен акаунт. Може да се нуждаете от правомощие за достъп до този машинен акаунт." + }, "unknownProject": { "message": "Неизвестен проект. Може да се нуждаете от правомощие за достъп до този проект." }, @@ -8565,7 +8611,7 @@ } } }, - "accessedProjectWithId": { + "accessedProjectWithIdentifier": { "message": "Осъществен е достъп до проект с идентификатор: $PROJECT_ID$.", "placeholders": { "project_id": { @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Изтрит е машинен акаунт с идентификатор: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Редактиран е проект с идентификатор: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Добавен е потребител: $USER_ID$ към машинен акаунт с идентификатор: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Премахнат е потребител: $USER_ID$ от машинен акаунт с идентификатор: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Премахната е група: $GROUP_ID$ от машинен акаунт с идентификатор: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Създаден е машинен акаунт с идентификатор: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Създадена е група: $GROUP_ID$ към машинен акаунт с идентификатор: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Изтрит е машинен акаунт с идентификатор: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Изтрит е проект с идентификатор: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Свързване" }, + "assignTasks": { + "message": "Назначаване на задачи" + }, "assignToCollections": { "message": "Свързване с колекции" }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 62dda4f7dd1..a0d38221ef2 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 65033f7d1d7..3f723a74e80 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index d1b5888dbef..90d69ca33ad 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Crea un nou element d'inici de sessió" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membres notificats ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assigna" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assigna a col·leccions" }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index b0e84d27a4c..62af2581a57 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Vytvořit novou položku přihlášení" }, + "percentageCompleted": { + "message": "$PERCENT$ % dokončeno", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ z $TOTAL$ bezpečnostních úkolů dokončeno", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Probíhá změna hesla" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Přiřadit úkoly členů ke sledování průběhu" + }, + "onceYouReviewApplications": { + "message": "Jakmile zkontrolujete a označíte aplikace jako kritické, zobrazí se zde." + }, + "sendReminders": { + "message": "Odeslat připomenutí" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Jakmile označíte aplikace jako kritické, zobrazí se zde" + "message": "Jakmile označíte aplikace jako kritické, zobrazí se zde." }, "viewAtRiskMembers": { "message": "Zobrazit ohrožené členy" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ ohrožených hesel", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Obeznámení členové ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Ohrožení členové" }, - "membersAtRiskActivityDescription": { - "message": "Členové, kteří upravují přístup k rizikovým položkám pro kritické aplikace" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Členové s přístupem k rizikovým položkám pro kritické aplikace" }, "membersAtRiskCount": { "message": "$COUNT$ členů v ohrožení", @@ -1500,7 +1543,7 @@ "message": "Chybné hlavní heslo" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Neplatné hlavní heslo. Potvrďte správnost e-mailu a zda byl Váš účet vytvořen na $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Neznámý tajný klíč. Možná budete muset požádat o oprávnění pro přístup k tomuto tajnému klíči." }, + "unknownServiceAccount": { + "message": "Neznámý strojový účet. Možná budete muset požádat o oprávnění pro přístup k tomuto strojovému účtu." + }, "unknownProject": { "message": "Neznámý projekt. Možná budete muset požádat o oprávnění pro přístup k tomuto projektu." }, @@ -8565,7 +8611,7 @@ } } }, - "accessedProjectWithId": { + "accessedProjectWithIdentifier": { "message": "Přístup k projektu s ID: $PROJECT_ID$.", "placeholders": { "project_id": { @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "ID smazaného strojového účtu: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Úprava projektu s ID: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Přidán uživatel $USER_ID$ do strojového účtu s ID $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Smazaný uživatel $USER_ID$ ze strojového účtu s ID $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Odebrána skupina $GROUP_ID$ ze strojového účtu s ID $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Vytvořený strojový účet s ID $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Přidána skupina $GROUP_ID$ do strojového účtu s ID $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Smazán strojový účet s ID $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Smazání projektu s ID: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Přiřadit" }, + "assignTasks": { + "message": "Přiřadit úkoly" + }, "assignToCollections": { "message": "Přiřadit ke sbírkám" }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 905707d7393..59dd63da969 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 2e175920a6d..2ffab8a80c9 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Opret nyt login-emne" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Underrettede medlemmer ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Udsatte medlemmer" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Tildel" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Tildel til samlinger" }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index eeac818b2f8..d45bf25a138 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Neuen Zugangsdaten-Eintrag erstellen" }, + "percentageCompleted": { + "message": "Zu $PERCENT$% abgeschlossen", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ von $TOTAL$ Sicherheitsaufgaben abgeschlossen", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Fortschritt der Passwortänderung" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Mitglieder Aufgaben zuweisen, um den Fortschritt zu überwachen" + }, + "onceYouReviewApplications": { + "message": "Sobald du Anwendungen überprüft und als kritisch markiert hast, werden sie hier angezeigt." + }, + "sendReminders": { + "message": "Erinnerungen senden" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Sobald du Anwendungen als kritisch markierst, werden sie hier angezeigt" + "message": "Sobald du Anwendungen als kritisch markierst, werden sie hier angezeigt." }, "viewAtRiskMembers": { "message": "Gefährdete Mitglieder anzeigen" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ Passwörter gefährdet", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Benachrichtigte Mitglieder ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Gefährdete Mitglieder" }, - "membersAtRiskActivityDescription": { - "message": "Mitglieder mit Berechtigung zum Bearbeiten gefährdeter Einträge für kritische Anwendungen" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Mitglieder mit Zugriff auf gefährdete Einträge für kritische Anwendungen" }, "membersAtRiskCount": { "message": "$COUNT$ gefährdete Mitglieder", @@ -1500,7 +1543,7 @@ "message": "Ungültiges Master-Passwort" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ungültiges Master-Passwort. Überprüfe, ob deine E-Mail-Adresse korrekt ist und dein Konto auf $HOST$ erstellt wurde.", "placeholders": { "host": { "content": "$1", @@ -1539,7 +1582,7 @@ "message": "Keine Suchergebnisse gefunden" }, "clearFiltersOrTryAnother": { - "message": "Filter löschen oder einen anderen Suchbegriff versuchen" + "message": "Filter löschen oder es mit einem anderen Suchbegriff versuchen" }, "noPermissionToViewAllCollectionItems": { "message": "Du hast nicht die Berechtigung, alle Einträge in dieser Sammlung anzuzeigen." @@ -4837,7 +4880,7 @@ "message": "Organisation ist deaktiviert" }, "organizationIsSuspendedDesc": { - "message": "Auf Einträge in deaktivierten Organisationen kann nicht zugegriffen werden. Kontaktiere den Eigentümer deiner Organisation, um Unterstützung zu erhalten." + "message": "Auf Einträge in deaktivierten Organisationen kann nicht zugegriffen werden. Kontaktiere den Besitzer deiner Organisation, um Unterstützung zu erhalten." }, "secretsAccessSuspended": { "message": "Auf deaktivierte Organisationen kann nicht zugegriffen werden. Bitte wende dich an deinen Organisationseigentümer." @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unbekanntes Geheimnis, möglicherweise müssen Sie eine Berechtigung anfordern, um auf dieses Geheimnis zuzugreifen." }, + "unknownServiceAccount": { + "message": "Unbekanntes Gerätekonto. Du musst möglicherweise eine Berechtigung anfordern, um auf dieses Gerätekonto zuzugreifen." + }, "unknownProject": { "message": "Unbekanntes Projekt. Du musst möglicherweise eine Berechtigung anfordern, um auf dieses Projekt zuzugreifen." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Auf ein Projekt mit der ID $PROJECT_ID$ zugegriffen.", + "accessedProjectWithIdentifier": { + "message": "Auf ein Projekt zugegriffen mit der Kennung: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Gelöschtes Gerätekonto mit der Kennung: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Ein Projekt mit der Kennung $PROJECT_ID$ bearbeitet", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Benutzer: $USER_ID$ hinzugefügt zu Gerätekonto mit der Kennung: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Benutzer: $USER_ID$ entfernt aus dem Gerätekonto mit der Kennung: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Gruppe: $GROUP_ID$ entfernt aus dem Gerätekonto mit der Kennung: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Gerätekonto erstellt mit der Kennung: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Gruppe: $GROUP_ID$ hinzugefügt zu Gerätekonto mit der Kennung: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Gelöschtes Gerätekonto mit der Kennung: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Ein Projekt mit der Kennung $PROJECT_ID$ gelöscht", "placeholders": { @@ -9111,13 +9236,13 @@ "message": "Konfiguriere das Sammlungs-Verhalten für die Organisation" }, "allowAdminAccessToAllCollectionItemsDescription": { - "message": "Erlaube Eigentümern und Administratoren, alle Sammlungen und Einträge über die Admin-Konsole zu verwalten" + "message": "Erlaube Besitzern und Administratoren, alle Sammlungen und Einträge über die Admin-Konsole zu verwalten" }, "restrictCollectionCreationDescription": { - "message": "Beschränke die Erstellung von Sammlungen auf Eigentümer und Administratoren" + "message": "Beschränke die Erstellung von Sammlungen auf Besitzer und Administratoren" }, "restrictCollectionDeletionDescription": { - "message": "Beschränke das Löschen von Sammlungen auf Eigentümer und Administratoren" + "message": "Beschränke das Löschen von Sammlungen auf Besitzer und Administratoren" }, "restrictItemDeletionDescriptionStart": { "message": "Beschränke das Löschen von Einträgen auf Mitglieder mit der ", @@ -9258,7 +9383,7 @@ } }, "allowAdminAccessToAllCollectionItemsEnabled": { - "message": "Die Einstellung Eigentümern und Administratoren erlauben, alle Sammlungen und Einträge zu verwalten wurde für $ID$ aktiviert.", + "message": "Die Einstellung Besitzern und Administratoren erlauben, alle Sammlungen und Einträge zu verwalten wurde für $ID$ aktiviert.", "placeholders": { "id": { "content": "$1", @@ -9267,7 +9392,7 @@ } }, "allowAdminAccessToAllCollectionItemsDisabled": { - "message": "Die Einstellung Eigentümern und Administratoren erlauben, alle Sammlungen und Einträge zu verwalten wurde für $ID$ deaktiviert.", + "message": "Die Einstellung Besitzern und Administratoren erlauben, alle Sammlungen und Einträge zu verwalten wurde für $ID$ deaktiviert.", "placeholders": { "id": { "content": "$1", @@ -9437,6 +9562,9 @@ "assign": { "message": "Zuweisen" }, + "assignTasks": { + "message": "Aufgaben zuweisen" + }, "assignToCollections": { "message": "Sammlungen zuweisen" }, @@ -9836,7 +9964,7 @@ "message": "Fehler beim Speichern der Integration. Bitte versuche es später erneut." }, "mustBeOrgOwnerToPerformAction": { - "message": "Du musst der Eigentümer der Organisation sein, um diese Aktion auszuführen." + "message": "Du musst der Besitzer der Organisation sein, um diese Aktion auszuführen." }, "failedToDeleteIntegration": { "message": "Löschen der Integration fehlgeschlagen. Bitte versuche es später erneut." @@ -11458,7 +11586,7 @@ "message": "Zusätzlicher Speicher GB" }, "additionalServiceAccountsV2": { - "message": "Zusätzliche Maschinenkonten" + "message": "Zusätzliche Gerätekonten" }, "secretsManagerSeats": { "message": "Secrets-Manager-Plätze" @@ -11560,7 +11688,7 @@ "message": "SCIM-Unterstützung" }, "includedMachineAccountsV2": { - "message": "$COUNT$ Maschinenkonten", + "message": "$COUNT$ Gerätekonten", "placeholders": { "count": { "content": "$1", @@ -11569,10 +11697,10 @@ } }, "enterpriseSecurityPolicies": { - "message": "Enterprise-Sicherheitsrichtlinien" + "message": "Sicherheitsrichtlinien für Unternehmen" }, "selfHostOption": { - "message": "Self-Host-Option" + "message": "Selbsthosting-Option" }, "complimentaryFamiliesPlan": { "message": "Kostenloser Families-Plan für alle Benutzer" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 5c84285054e..436f2176efb 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Ειδοποιημένα μέλη ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Ανάθεση" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Ανάθεση σε συλλογές" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index a75d0369c1e..e5e5d8f972c 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7044,7 +7087,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported.", "placeholders": { "organization": { "content": "$1", @@ -7053,7 +7096,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", "placeholders": { "organization": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index b032523c548..c00b02f1e6f 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7044,7 +7087,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported.", "placeholders": { "organization": { "content": "$1", @@ -7053,7 +7096,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", "placeholders": { "organization": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 2fa2257ef4f..4634635f277 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Krei novan salutan eron" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Sciigitaj membroj ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index f5e2629db6f..85305249a91 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Crear nuevo elemento de inicio de sesión" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Miembros notificados ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Asignar" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Asignar a colecciones" }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index e540073d369..23c1a1312eb 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Loo uus kirje" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Teavitatud liikmed ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 098cc94ef65..8e86873841d 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 4bdd8ecadc2..10b51babfc1 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "ایجاد مورد ورود جدید" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "اعضا مطلع شدند ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "اعضای در معرض خطر" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "اختصاص بدهید" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "اختصاص به مجموعه‌ها" }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index cfb9af1ffd2..54662ea2274 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Luo uusi kirjautumiskohde" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Ilmoitetut jäsenet ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Riskialttiit jäsenet" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Määritä" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Määritä kokoelmiin" }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 37f90dcdfe2..96c97dcc6d5 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 73b9757694d..62fdc6741b2 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Créer un nouvel élément de connexion" }, + "percentageCompleted": { + "message": "$PERCENT$% complété", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ sur $TOTAL$ tâches de sécurité terminées", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Progression du changement de mot de passe" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Affecter des tâches aux membres pour surveiller la progression" + }, + "onceYouReviewApplications": { + "message": "Une fois que vous avez évalué et marqué des applications comme critiques, elles s'afficheront ici." + }, + "sendReminders": { + "message": "Envoyer des rappels" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Une fois que vous avez marqué des applications comme critiques, elles s'afficheront ici" + "message": "Une fois que vous avez marqué les applications critiques, elles s'afficheront ici." }, "viewAtRiskMembers": { "message": "Afficher les membres à risque" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ mots de passe à risque", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membres notifiés ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Membres à risque" }, - "membersAtRiskActivityDescription": { - "message": "Membres pouvant modifier les éléments à risque pour les applications critiques" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Les membres ayant accès aux éléments à risque pour les applications critiques" }, "membersAtRiskCount": { "message": "$COUNT$ membres à risque", @@ -1500,7 +1543,7 @@ "message": "Mot de passe principal invalide" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Mot de passe principal invalide. Confirmez que votre adresse courriel est correcte et que votre compte a été créé sur $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Secret inconnu, vous devrez peut-être demander l'autorisation d'accéder à ce secret." }, + "unknownServiceAccount": { + "message": "Compte machine inconnu, vous devrez peut-être demander l'autorisation d'accéder à ce compte machine." + }, "unknownProject": { "message": "Projet inconnu, vous devrez peut-être demander l'autorisation d'accéder à ce projet." }, @@ -8565,7 +8611,7 @@ } } }, - "accessedProjectWithId": { + "accessedProjectWithIdentifier": { "message": "A accédé à un projet avec l'identifiant : $PROJECT_ID$.", "placeholders": { "project_id": { @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "A supprimé le compte machine ID : $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "A modifié un projet avec l'identifiant : $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Utilisateur ajouté : $USER_ID$ au compte machine avec l'ID : $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "A supprimé l'utilisateur : $USER_ID$ du compte machine avec l'ID : $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "A supprimé le groupe : $GROUP_ID$ du compte machine avec l'ID : $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "A créé le compte machine avec l'ID : $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "A ajouté le groupe : $GROUP_ID$ du compte machine avec l'ID : $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "A supprimé le compte machine avec l'ID : $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "A supprimé un projet avec l'identifiant : $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assigner" }, + "assignTasks": { + "message": "Assigner des tâches" + }, "assignToCollections": { "message": "Assigner aux collections" }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index b211482fe29..c4c35278f4b 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index af4548a202d..915145eebc5 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "צור פריט כניסה חדש" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "חברים שהודיעו להם ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "חברים בסיכון" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "סוד לא ידוע, ייתכן שאתה צריך לבקש הרשאה כדי לגשת אל סוד זה." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "פרויקט לא ידוע, ייתכן שאתה צריך לבקש הרשאה כדי לגשת אל פרויקט זה." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "ניגש אל פרויקט עם מזהה: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "ערך פרויקט עם מזהה: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "מחק פרויקט עם מזהה: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "הקצה" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "הקצה לאוספים" }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index c9a1372b54f..f7d116162ff 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 4fdae01443c..8f49041638a 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Stvori novu stavku prijave" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "Rizični korisnici" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Obaviješteni članovi ($COUNT$)", "placeholders": { @@ -162,11 +205,11 @@ "atRiskMembers": { "message": "Rizični korisnici" }, - "membersAtRiskActivityDescription": { - "message": "Članovi koji mogu uređivati stavke za aplikacije označene kao kritične" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { - "message": "$COUNT$ members at-risk", + "message": "Rizičnih članova: $COUNT$", "placeholders": { "count": { "content": "$1", @@ -1500,7 +1543,7 @@ "message": "Neispravna glavna lozinka" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Nevažeća glavna lozinka. Provjeri je li tvoja adresa e-pošta ispravna i je li račun kreiran na $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1518,28 +1561,28 @@ "message": "Nema stavki za prikaz." }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Nema stavki u smeću" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "Stavke koje obrišeš biti će premještene ovdje, a nakon 30 dana biti će trajno izbrisane" }, "noItemsInVault": { - "message": "No items in the vault" + "message": "Nema stavaka u trezoru" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "Trezor štiti više od lozinki. Sigurno spremi prijave, identitete, kartice i bilješke." }, "emptyFavorites": { - "message": "You haven't favorited any items" + "message": "Niti jedna stavka nije dodana u favorite" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "Za brzi pristup često korištenim stavkama dodaj ih u favorite." }, "noSearchResults": { - "message": "No search results returned" + "message": "Nema rezultata pretrage" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Očisti filtre ili pokušaj s drugačijom pretragom" }, "noPermissionToViewAllCollectionItems": { "message": "Nemaš prava vidjeti sve stavke u ovoj zbirci." @@ -4834,10 +4877,10 @@ "message": "Organizacija suspendirana" }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "Organizacija je suspendirana" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "Stavkama u suspendiranoj Organizaciji se ne može pristupiti. Kontaktiraj vlasnika Organizacije za pomoć." }, "secretsAccessSuspended": { "message": "Stavkama u suspendiranoj Organizaciji se ne može pristupiti. Kontaktiraj vlasnika Organizacije za pomoć." @@ -5225,11 +5268,11 @@ "message": "SSO identifikator" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "message": "Daj ovaj ID svojim članovima za prijavu putem SSO-a. Članovi mogu preskočiti unos ovog identifikatora tijekom SSO-a ako je postavljena domena za koju su prijavljeni. ", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "Saznaj više", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -7044,7 +7087,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Izvezt će se samo trezor organizacije povezan s $ORGANIZATION$.", "placeholders": { "organization": { "content": "$1", @@ -7053,7 +7096,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Izvezt će se samo trezor organizacije povezan s $ORGANIZATION$. Zbirka mojih stavki neće biti uključena.", "placeholders": { "organization": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Nepoznata tajna. Možda trebaš tražiti dozvolu za pristup ovoj tajni." }, + "unknownServiceAccount": { + "message": "Nepoznati mašinski račun. Možda trebaš tražiti dozvolu za pristup ovom mašinskom računu." + }, "unknownProject": { "message": "Nepoznati projekt. Možda trebaš tražiti dozvolu za pristup ovom projektu." }, @@ -8565,7 +8611,7 @@ } } }, - "accessedProjectWithId": { + "accessedProjectWithIdentifier": { "message": "Pristupljeno projektu s identifikatorom: $PROJECT_ID$.", "placeholders": { "project_id": { @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Obrisan mašinski račun s id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Uređen projekt s identifikatorom: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Korisnik: $USER_ID$ dodan mašinskom računu $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Korisnik $USER_ID$ uklonjen iz mašinskog računa $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Grupa $GROUP_ID$ uklonjena iz mašinskog računa$SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Stvoren mašinski račun $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Grupa $GROUP_ID$ dodana mašinskom računu $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Obrisan mašinski račun $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Izbrisan projekt s identifkatorom: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Dodijeli" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Dodijeli zbirkama" }, @@ -11102,11 +11230,11 @@ "message": "Pretraži arhivu" }, "archiveNoun": { - "message": "Archive", + "message": "Arhiva", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arhiviraj", "description": "Verb" }, "noItemsInArchive": { @@ -11223,10 +11351,10 @@ "message": "Bitwarden proširenje je instalirano!" }, "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" + "message": "Otvori Bitwarden proširenje" }, "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "message": "Instalirano je Bitwarden proširenje! Otvori ga za prijavu i početak auto-ispune." }, "openExtensionToAutofill": { "message": "Otvori proširenje i prijavi se za početak korištenja auto-ispune." diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index c4b6fcf08c1..cfc6e9e0735 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -59,6 +59,40 @@ "createNewLoginItem": { "message": "Új bejelentkezési elem létrehozása" }, + "percentageCompleted": { + "message": "$PERCENT$% kész", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ / $TOTAL$ biztonsági feladatok elvégzésre került.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Jelszócsere feldolgozás" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Rendeljünk a tagokhoz feladatokat az előrehaladás monitorozására." + }, + "onceYouReviewApplications": { + "message": "Az alkalmazások áttekintése és kritikusnak jelölése után ezek itt jelennek meg." + }, + "sendReminders": { + "message": "Emlékeztető küldés" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { "message": "A kritikus alkalmazások megjelölésével azok itt jelennek meg." }, @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ kockázatos jelszó", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Értesített tagok ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Veszélyes tagok" }, - "membersAtRiskActivityDescription": { - "message": "Tagok szerkesztési hozzáféréssel a kritikus alkalmazások veszélyeztetett elemeihez" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ kockázatos tag", @@ -1500,7 +1543,7 @@ "message": "A mesterjelszó érvénytelen." }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "A mesterjelszó érvénytelen. Erősítsük meg, hogy email cím helyes és a fiók létrehozásának helye: $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Ismeretlen a titkos kód, előfordulhat, hogy engedélyt kell kérni a titkos kód eléréséhez." }, + "unknownServiceAccount": { + "message": "A gép fiók ismeeretlen, előfordulhat, hogy engedélyt kell kérni a gép fiók eléréséhez." + }, "unknownProject": { "message": "Ismeretlen a projekt, előfordulhat, hogy engedélyt kell kérni a projekt eléréséhez." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Egy projekt hozzáférésre került egy azonosítóval: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Egy projekt elérése megtörtént egy azonosítóval: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "A gép fiók törlésre került egy azonosítóval: $SERVICE_ACCOUNT_ID$.", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Egy projekt szerkesztése megtörtént egy azonosítóval: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Egy felhasználó került hozzáadásra: $USER_ID$ a gépi fiókhoz egy azonosítóval: $SERVICE_ACCOUNT_ID$.", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Egy felhasználó lett eltávolítva: $USER_ID$ a gépi fiókból egy azonosítóval: $SERVICE_ACCOUNT_ID$.", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Egy csoport lett eltávolítva: $GROUP_ID$ a gépi fiókból egy azonosítóval: $SERVICE_ACCOUNT_ID$.", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Egy gép fiók lett létrehozva egy azonosítóval: $SERVICE_ACCOUNT_ID$.", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Egy csoport lett hozzáadva: $GROUP_ID$ a gép fiókhoz egy azonosítóval: $SERVICE_ACCOUNT_ID$.", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Egy gép fiók került tötlésre egy azonosítóval: $SERVICE_ACCOUNT_ID$.", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Egy projekt törlése megtörtént egy azonosítóval: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Hozzárendelés" }, + "assignTasks": { + "message": "Feladatok hozzárendelése" + }, "assignToCollections": { "message": "Hozzárendelés gyűjteményekhez" }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 508f9b853d4..3a628b0889b 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Buat entri masuk baru" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Anggota yang diberitahukan ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 68479fee66f..53eece391a5 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Crea nuovo elemento di login" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membri notificati ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Membri a rischio" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Segreto sconosciuto, potrebbe essere necessario richiedere l'autorizzazione per l'accesso." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Progetto sconosciuto, potrebbe essere necessario richiedere l'autorizzazione per l'accesso." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accesso al progetto con identificatore $PROJECT_ID$ riuscito.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Progetto con identificatore $PROJECT_ID$ modificato", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Progetto con identificatore $PROJECT_ID$ eliminato", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assegna" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assegna alle raccolte" }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index ef7e01f927b..bd8a9465b7b 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "新しいログインアイテムを作成" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "通知済みメンバー ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "リスクがあるメンバー" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "割り当て" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "コレクションに割り当てる" }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index c5f62bb779a..a57142dc31f 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 70a88883ec4..173520dab61 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 50b28bb0836..d625ab92f07 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 10894c6e795..2ebd08b1733 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 9cbac624833..28bca33cdd5 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Izveidot jaunu pieteikšanās vienumu" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Tikldīz lietotnes tiks atzīmētas kā būtiskas, tās tiks parādītas šeit" + "message": "Tiklīz lietotnes tiks atzīmētas kā būtiskas, tās tiks parādītas šeit." }, "viewAtRiskMembers": { "message": "Apskatīt riskam pakļautos dalībniekus" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Apziņotie dalībnieki ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Riskam pakļautie dalībnieki" }, - "membersAtRiskActivityDescription": { - "message": "Dalībnieki ar labošanas piekluvi riskam pakļautajiem vienumiem būtiskajām lietotnēm" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Dalībnieki ar piekluvi riskam pakļautajiem vienumiem būtiskajām lietotnēm" }, "membersAtRiskCount": { "message": "$COUNT$ dalībnieki ir pakļauti riskam", @@ -1500,7 +1543,7 @@ "message": "Nederīga galvenā parole" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Nederīga galvenā parole. Jāpārliecinās, ka e-pasta adrese ir pareiza un konts tika izveidots $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Nezināms noslēpums, var būt nepieciešams pieprasīt atļauju piekļūt tam." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Nezināms projekts, var būt nepieciešams pieprasīt atļauju piekļūt tam." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Piekļuva projektam ar Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Laboja projektu ar identifikatoru: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Izdzēsa projektu ar identifikatoru: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Piešķirt" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Piešķirt krājumiem" }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index da058b2ab25..cbb5aeecf49 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index db36040f830..2ea56d367ff 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "नवीन लॉगिन आयटम तयार करा" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 70a88883ec4..173520dab61 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index c3a70fa4609..778d3adf74c 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Knytt" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Legg til i samlinger" }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 15bd69be119..6575f687031 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 6b6777d460c..31d648fb94f 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Nieuw login item aanmaken" }, + "percentageCompleted": { + "message": "$PERCENT$% compleet", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ van $TOTAL$ beveiligingstaken voltooid", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Voortgang veranderen wachtwoord" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Leden taken toewijzen om de voortgang te controleren" + }, + "onceYouReviewApplications": { + "message": "Als je toepassingen als belangrijk markeert, verschijnen ze hier." + }, + "sendReminders": { + "message": "Herinneringen versturen" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Als je toepassingen als belangrijk markeert, verschijnen ze hier" + "message": "Als je toepassingen als belangrijk markeert, verschijnen ze hier." }, "viewAtRiskMembers": { "message": "Leden met risico bekijken" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ wachtwoorden lopen risico", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Geînformeerde leden ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Leden in gevaar" }, - "membersAtRiskActivityDescription": { - "message": "Leden met toegang voor bewerken van risico-items voor belangrijke applicaties" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Leden met toegang tot risico-items voor belangrijke applicaties" }, "membersAtRiskCount": { "message": "$COUNT$ leden lopen risico", @@ -1500,7 +1543,7 @@ "message": "Ongeldig hoofdwachtwoord" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ongeldig hoofdwachtwoord. Check of je e-mailadres klopt en of je account is aangemaakt op $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Onbekend secret, je moet misschien toegang vragen om dit secret te benaderen." }, + "unknownServiceAccount": { + "message": "Onbekend machine-account, je moet misschien toegang vragen om dit machine-acount te benaderen." + }, "unknownProject": { "message": "Onbekend project, je moet misschien toegang vragen om dit project te benaderen." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Project gekoppeld met Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Project bekeken met identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Machine-account ID verwijderd: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Project bewerkt met identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Gebruiker toegevoegd: $USER_ID$ naar machine-account met identificatie: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Gebruiker verwijderd: $USER_ID$ van machine-account met identificatie: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Groep verwijderd: $USER_ID$ van machine-account met identificatie: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Machine-account aangemaakt met identificatie: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Groep toegevoegd: $GROUP_ID$ van machine-account met identificatie: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Machine-account verwijderd met identificatie: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Project verwijderd met identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Toewijzen" }, + "assignTasks": { + "message": "Taken toewijzen" + }, "assignToCollections": { "message": "Toewijzen aan collecties" }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index d30b23e35db..92bfe3c925c 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 70a88883ec4..173520dab61 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 43dc88f05b8..808981f5fb0 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Utwórz nowy element logowania" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Powiadomieni członkowie ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Zagrożeni użytkownicy" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Przypisz" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Przypisz do kolekcji" }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 0a1a4223df3..5df0689d9de 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Criar item de \"login\"" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membros notificados ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Membros de risco" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Atribuir" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Atribuir à coleções" }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 9bb89f881ac..a7be84317e3 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Criar nova credencial" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Depois de marcar as aplicações como críticas, estas serão apresentadas aqui" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "Ver membros em risco" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Membros notificados ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Membros em risco" }, - "membersAtRiskActivityDescription": { - "message": "Membros com acesso de edição a itens em risco para aplicações críticas" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ membros em risco", @@ -1500,7 +1543,7 @@ "message": "Palavra-passe mestra inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Palavra-passe mestra inválida. Confirme se o seu e-mail está correto e se a sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7044,7 +7087,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado.", "placeholders": { "organization": { "content": "$1", @@ -7053,7 +7096,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. As coleções dos meus itens não serão incluídas.", "placeholders": { "organization": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Segredo desconhecido, poderá ser necessário pedir autorização para aceder a este segredo." }, + "unknownServiceAccount": { + "message": "Conta automática desconhecida, poderá precisar de pedir autorização para aceder a esta conta automática." + }, "unknownProject": { "message": "Projeto desconhecido, poderá ser necessário pedir autorização para aceder a este projeto." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Acedeu a um projeto com o ID: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Acedeu a um projeto com o identificador: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "ID da conta automática eliminada: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Editou um projeto com o identificador: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Utilizador adicionado: $USER_ID$ à conta automática com o identificador: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Utilizador removido: $USER_ID$ da conta automática com o identificador: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Grupo removido: $GROUP_ID$ da conta automática com o identificador: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Conta automática criada com o identificador: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Grupo adicionado: $GROUP_ID$ à conta automática com o identificador: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Conta automática eliminada com o identificador: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Eliminou um projeto com o identificador: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Atribuir" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Atribuir às coleções" }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 8aaeaf7d87c..3296b1a3a0a 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 9808fae953e..8db4da0c6ca 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Создать новый логин" }, + "percentageCompleted": { + "message": "Завершено: $PERCENT$%", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "Завершено $COUNT$ из $TOTAL$ задач безопасности", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Прогресс смены пароля" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Уведомленные участники ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Участники группы риска" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -1500,7 +1543,7 @@ "message": "Неверный мастер-пароль" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Неверный мастер-пароль. Подтвердите, что ваш адрес email указан верно и ваш аккаунт был создан на $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Неизвестный секрет. Вам может потребоваться запросить разрешение на доступ к этому секрету." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Неизвестный проект. Вам может потребоваться запросить разрешение на доступ к этому проекту." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Получен доступ к проекту с ID: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Изменен проект с идентификатором: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Удален проект с идентификатором: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Назначить" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Назначить коллекциям" }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index dab19f43acd..8db4f703b7e 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 32c26b6f9ca..5bea697ab57 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Pridať novu položku s prihlásením" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "Zobraziť ohrozených členov" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,11 +205,11 @@ "atRiskMembers": { "message": "Ohrozených členov" }, - "membersAtRiskActivityDescription": { - "message": "Členovia s oprávnením upravovať ohrozené položky kritických aplikácii" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { - "message": "$COUNT$ members at-risk", + "message": "$COUNT$ ohrozených členov", "placeholders": { "count": { "content": "$1", @@ -1518,28 +1561,28 @@ "message": "Neexistujú žiadne položky na zobrazenie." }, "noItemsInTrash": { - "message": "No items in trash" + "message": "V koši nie sú žiadne položky" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "Položky, ktoré odstránite, sa zobrazia tu a po 30 dňoch sa odstránia natrvalo" }, "noItemsInVault": { - "message": "No items in the vault" + "message": "V trezore nie sú žiadne položky" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "Trezor chráni viac ako len heslá. Môžete tu bezpečne ukladať prihlasovacie údaje, identifikačné údaje, karty a poznámky." }, "emptyFavorites": { - "message": "You haven't favorited any items" + "message": "Neoznačili ste žiadnu položku za obľúbenú" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "Pre rýchly prístup, pridajte položky medzi obľúbené." }, "noSearchResults": { - "message": "No search results returned" + "message": "Nenašli sa žiadne výsledky vyhľadávania" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Vymažte filtre alebo zmeňte vyhľadávaný výraz" }, "noPermissionToViewAllCollectionItems": { "message": "Nemáte povolenie pre zobrazenie všetkých položiek v tejto zbierke." @@ -4834,10 +4877,10 @@ "message": "Organizácia je vypnutá." }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "Organizácia je pozastavená" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "K položkám v pozastavenej organizácii nie je možné pristupovať. Požiadajte o pomoc vlastníka organizácie." }, "secretsAccessSuspended": { "message": "K pozastaveným organizáciám nie je možné pristupovať. Požiadajte o pomoc vlastníka organizácie." @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Neznáma položka možno potrebujete požiadať o prístup k tejto položke." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Neznámy projekt, možno potrebujete požiadať o prístup k tomuto projektu." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Pristúp k projektu s identifikátorom: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Upravený projekt s identifikátorom: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Odstránený projekt s identifikátorom: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Prideliť" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Prideliť k zbierkam" }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 944faf782cf..f0acd2ca34b 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index a0c93cc0ebb..1eaf8f25274 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Kreirajte novu stavku za prijavu" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Obavešteni članovi ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index 8a17ca2dd9e..e31c138121f 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -59,17 +59,51 @@ "createNewLoginItem": { "message": "Креирајте нову ставку за пријаву" }, + "percentageCompleted": { + "message": "$PERCENT$% завршено", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "Завршени сигурносни задаци: $COUNT$ од $TOTAL$", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Напредак промене лозинке" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Доделите задатке чланова да прате напредак" + }, + "onceYouReviewApplications": { + "message": "Једном када прегледате апликације и означите их као критичне, оне ће се овде приказати." + }, + "sendReminders": { + "message": "Пошаљи подсетнике" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Апликације обележене као критичне, су приказане овде." }, "viewAtRiskMembers": { - "message": "View at-risk members" + "message": "Преглед чланова под ризиком" }, "viewAtRiskApplications": { - "message": "View at-risk applications" + "message": "Преглед апликација под ризиком" }, "criticalApplicationsAreAtRisk": { - "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "message": "Критичне апликације ризиковне због ризичнр лозинке: $COUNT$ од $TOTAL$", "placeholders": { "count": { "content": "$1", @@ -91,7 +125,7 @@ } }, "countOfCriticalApplications": { - "message": "$COUNT$ critical applications", + "message": "Критичне апликације: $COUNT$", "placeholders": { "count": { "content": "$1", @@ -100,7 +134,16 @@ } }, "countOfApplicationsAtRisk": { - "message": "$COUNT$ applications at-risk", + "message": "Апликације у ризику: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "countOfAtRiskPasswords": { + "message": "Лозинке у ризику: $COUNT$", "placeholders": { "count": { "content": "$1", @@ -162,11 +205,11 @@ "atRiskMembers": { "message": "Чланови под ризиком" }, - "membersAtRiskActivityDescription": { - "message": "Чланови са правом уређивања за угрожене ставке критичних апликација" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Чланови са приступом за угрожене ставке критичних апликација" }, "membersAtRiskCount": { - "message": "$COUNT$ members at-risk", + "message": "Угрожени чланови: $COUNT$", "placeholders": { "count": { "content": "$1", @@ -1500,7 +1543,7 @@ "message": "Погрешна главна лозинка" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Неважећа главна лозинка. Потврдите да је ваш имејл тачан и ваш рачун је креиран на $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1518,28 +1561,28 @@ "message": "Нама ставке у листи." }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Нема ставки у смећу" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "Ставке које избришете ће се појавити овде и биће трајно избрисане након 30 дана" }, "noItemsInVault": { - "message": "No items in the vault" + "message": "Сеф је празан" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "Сеф штити више од само ваших лозинки. Овде безбедно чувајте безбедне пријаве, личне карте, картице и белешке." }, "emptyFavorites": { - "message": "You haven't favorited any items" + "message": "Нисте фаворизовали никакве ставке" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "Додајте често кориштене ставке у фаворите за брз приступ." }, "noSearchResults": { - "message": "No search results returned" + "message": "Нема резултата претраге" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Обришите филтере или покушајте са другим термином" }, "noPermissionToViewAllCollectionItems": { "message": "Немате дозволу да видите све ставке у овој колекцији." @@ -4834,7 +4877,7 @@ "message": "Организација је онемогућена." }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "Организација је онемогућена" }, "organizationIsSuspendedDesc": { "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." @@ -5229,7 +5272,7 @@ "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "Сазнај више", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -6638,7 +6681,7 @@ "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." + "message": "Није могуће прихватити понуду. Пошаљите имејл са свог Enterprise налога и покушајте поново." }, "sponsoredFamiliesAcceptFailedShort": { "message": "Није могуће прихватити понуду. $DESCRIPTION$", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -7475,7 +7521,7 @@ "message": "Променити кључ" }, "scimApiKey": { - "message": "SCIM API key", + "message": "SCIM API кључ", "description": "the text, 'SCIM' and 'API', are acronyms and should not be translated." }, "copyScimUrl": { @@ -7519,7 +7565,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Следећи знакови нису дозвољени: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -7585,10 +7631,10 @@ "message": "DUO пријава у два корака је потребна за ваш налог." }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "За ваш налог је потребан два корака. Следите наведене кораке да бисте завршили пријављивање." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "Следите наведене кораке да бисте завршили пријављивање." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { "message": "Следите наведене кораке да бисте завршили пријаву са својим безбедносним кључем." @@ -8469,7 +8515,7 @@ "message": "Менаџер тајни" }, "secretsManagerAccessDescription": { - "message": "Activate user access to Secrets Manager." + "message": "Активирај приступ кориснику Secrets Manager-у." }, "userAccessSecretsManagerGA": { "message": "Овај корисник може приступити Менаџеру Тајни" @@ -8539,7 +8585,7 @@ } }, "permanentlyDeletedSecretWithId": { - "message": "Permanently deleted a secret with identifier: $SECRET_ID$", + "message": "Тајна трајно избрисана са идентификатором: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8548,7 +8594,7 @@ } }, "restoredSecretWithId": { - "message": "Restored a secret with identifier: $SECRET_ID$", + "message": "Тајна враћена са идентификатором: $SECRET_ID$", "placeholders": { "secret_id": { "content": "$1", @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Приступљен пројекат са ИД: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Уређен пројекат са ИД: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Избрисан пројекат са ИД: $PROJECT_ID$", "placeholders": { @@ -9021,7 +9146,7 @@ "message": "Недостаје имејл корисника" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "Имејл активног корисника није пронађен. Одјављивање." }, "deviceTrusted": { "message": "Уређај поуздан" @@ -9030,7 +9155,7 @@ "message": "Позови кориснике" }, "secretsManagerForPlan": { - "message": "Secrets Manager for $PLAN$", + "message": "Secrets Manager за $PLAN$", "placeholders": { "plan": { "content": "$1", @@ -9437,6 +9562,9 @@ "assign": { "message": "Додели" }, + "assignTasks": { + "message": "Додели задатке" + }, "assignToCollections": { "message": "Додели колекцијама" }, @@ -9444,10 +9572,10 @@ "message": "Додели овим колекцијама" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "Само чланови организације са приступом овим збиркама ће моћи да виде ставку." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Само чланови организације са приступом овим збиркама ће моћи да виде ставке." }, "selectCollectionsToAssign": { "message": "Изаберите колекције за доделу" @@ -10062,7 +10190,7 @@ "message": "Управљени провајдери сервиса" }, "managedServiceProvider": { - "message": "Managed service provider" + "message": "Управљачки провајдер сервиса" }, "multiOrganizationEnterprise": { "message": "Multi-organization enterprise" @@ -10758,16 +10886,16 @@ "message": "Ваш рачун је пријављен на сваку од доле наведених уређаја." }, "claimedDomains": { - "message": "Claimed domains" + "message": "Захтевани домене" }, "claimDomain": { - "message": "Claim domain" + "message": "Захтевај домен" }, "reclaimDomain": { - "message": "Reclaim domain" + "message": "Поновно уахтевање домена" }, "claimDomainNameInputHint": { - "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + "message": "Пример: mydomain.com. Под-домени захтевају посебне уносе да би били захтевани." }, "automaticClaimedDomains": { "message": "Automatic Claimed Domains" @@ -11102,11 +11230,11 @@ "message": "Претражи архиву" }, "archiveNoun": { - "message": "Archive", + "message": "Архива", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архива", "description": "Verb" }, "noItemsInArchive": { @@ -11119,10 +11247,10 @@ "message": "Пословна јединица" }, "businessUnits": { - "message": "Business Units" + "message": "Пословне јединице" }, "newBusinessUnit": { - "message": "New business unit" + "message": "Нова пословна јединица" }, "sendsTitleNoItems": { "message": "Шаљите бзбедно осетљиве информације", @@ -11223,7 +11351,7 @@ "message": "Bitwarden екстензија инсталираана!" }, "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" + "message": "Отворити Bitwarden екстензију" }, "bitwardenExtensionInstalledOpenExtension": { "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." @@ -11276,10 +11404,10 @@ "description": "Error message shown when trying to add credit to a trialing organization without a billing address." }, "aboutThisSetting": { - "message": "About this setting" + "message": "О овом подешавању" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "Bitwarden ће користити сачуване URI-јеве за пријаву да би одредио коју икону или URL за промену лозинке треба користити како би побољшао ваше искуство. Никакви подаци нису сакупљени нити сачувани приликом коришћења ове услуге." }, "billingAddress": { "message": "Адреса рачуна" @@ -11381,7 +11509,7 @@ "message": "Неограничене тајне и пројекте" }, "providersubscriptionCanceled": { - "message": "Subscription canceled" + "message": "Претплата је отказана" }, "providersubCanceledmessage": { "message": "To resubscribe, contact Bitwarden Customer Support." @@ -11494,13 +11622,13 @@ "message": "Advanced capabilities for any organization" }, "planNameCustom": { - "message": "Custom plan" + "message": "Прилагођени план" }, "planDescCustom": { "message": "Bitwarden scales with businesses of all sizes to secure passwords and sensitive information. If you're part of a large enterprise, contact sales to request a quote." }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Уграђени аутентификатор" }, "breachMonitoring": { "message": "Праћење повreda безбедности" @@ -11515,7 +11643,7 @@ "message": "Неограничено дељење – изаберите ко шта може да види" }, "familiesUnlimitedCollections": { - "message": "Unlimited family collections" + "message": "Неограничене фамилија колекције" }, "familiesSharedStorage": { "message": "Заједничко складиштење важних породичних информација" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 5fa95a53fd0..9523343079c 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Skapa nytt inloggningsobjekt" }, + "percentageCompleted": { + "message": "$PERCENT$% färdigt", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ av $TOTAL$ säkerhetsuppgifter slutförda", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Skicka påminnelser" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "När du markerar applikationer som kritiska så kommer de att visas här" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "Visa medlemmar i riskzonen" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Meddelade medlemmar ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Riskutsatta medlemmar" }, - "membersAtRiskActivityDescription": { - "message": "Medlemmar med redigeringsbehörighet till objekt i riskzonen för kritiska applikationer" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ medlemmar i riskzonen", @@ -1500,7 +1543,7 @@ "message": "Ogiltigt huvudlösenord" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ogiltigt huvudlösenord. Bekräfta att din e-postadress är korrekt och ditt konto skapades på $HOST$.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Okänd hemlighet, du kan behöver begära behörighet för att komma åt denna hemlighet." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Okänt projekt, du kan behöver begära behörighet för att komma åt detta projekt." }, @@ -7757,11 +7803,11 @@ "description": "Label for a secret (key/value pair)" }, "serviceAccount": { - "message": "Tjänstkonto", + "message": "Tjänstekonto", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "serviceAccounts": { - "message": "Tjänstkonton", + "message": "Tjänstekonton", "description": "The title for the section that deals with service accounts." }, "secrets": { @@ -7785,7 +7831,7 @@ "description": "Title for creating a new secret." }, "newServiceAccount": { - "message": "Nytt tjänstkonto", + "message": "Nytt tjänstekonto", "description": "Title for creating a new service account." }, "secretsNoItemsTitle": { @@ -7800,7 +7846,7 @@ "message": "Det finns inga hemligheter i papperskorgen." }, "serviceAccountsNoItemsMessage": { - "message": "Skapa ett nytt tjänstkonto för att komma igång med automatisering av hemlig åtkomst.", + "message": "Skapa ett nytt tjänstekonto för att komma igång med automatisering av hemlig åtkomst.", "description": "Message to encourage the user to start creating service accounts." }, "serviceAccountsNoItemsTitle": { @@ -7812,19 +7858,19 @@ "description": "Placeholder text for searching secrets." }, "deleteServiceAccounts": { - "message": "Ta bort tjänstkonton", + "message": "Ta bort tjänstekonton", "description": "Title for the action to delete one or multiple service accounts." }, "deleteServiceAccount": { - "message": "Ta bort tjänstkonto", + "message": "Ta bort tjänstekonto", "description": "Title for the action to delete a single service account." }, "viewServiceAccount": { - "message": "Visa tjänstkonto", + "message": "Visa tjänstekonto", "description": "Action to view the details of a service account." }, "deleteServiceAccountDialogMessage": { - "message": "Att ta bort tjänstkontot $SERVICE_ACCOUNT$ är permanent och kan inte ångras.", + "message": "Att ta bort tjänstekontot $SERVICE_ACCOUNT$ är permanent och kan inte ångras.", "placeholders": { "service_account": { "content": "$1", @@ -7833,7 +7879,7 @@ } }, "deleteServiceAccountsDialogMessage": { - "message": "Att ta bort tjänstkonton är permanent och kan inte ångras." + "message": "Att ta bort tjänstekonton är permanent och kan inte ångras." }, "deleteServiceAccountsConfirmMessage": { "message": "Radera $COUNT$ tjänstkonton", @@ -7848,14 +7894,14 @@ "message": "Servicekonto raderat" }, "deleteServiceAccountsToast": { - "message": "Tjänstkonton togs bort" + "message": "Tjänstekonton togs bort" }, "searchServiceAccounts": { - "message": "Sök efter tjänstkonton", + "message": "Sök efter tjänstekonton", "description": "Placeholder text for searching service accounts." }, "editServiceAccount": { - "message": "Redigera tjänstkonto", + "message": "Redigera tjänstekonto", "description": "Title for editing a service account." }, "addProject": { @@ -7908,15 +7954,15 @@ "description": "" }, "serviceAccountName": { - "message": "Tjänstkontonamn", + "message": "Tjänstekontonamn", "description": "Label for the name of a service account" }, "serviceAccountCreated": { - "message": "Tjänstkonto skapat", + "message": "Tjänstekonto skapat", "description": "Notifies that a new service account has been created" }, "serviceAccountUpdated": { - "message": "Tjänstkonto uppdaterat", + "message": "Tjänstekonto uppdaterat", "description": "Notifies that a service account has been updated" }, "typeOrSelectProjects": { @@ -8445,13 +8491,13 @@ "message": "Lägg till personer eller grupper för att börja samarbeta" }, "projectEmptyServiceAccountAccessPolicies": { - "message": "Lägg till tjänstkonton för att bevilja åtkomst" + "message": "Lägg till tjänstekonton för att bevilja åtkomst" }, "serviceAccountPeopleDescription": { "message": "Ge grupper eller personer åtkomst till detta servicekonto." }, "serviceAccountProjectsDescription": { - "message": "Tilldela projekt till detta tjänstkonto. " + "message": "Tilldela projekt till detta tjänstekonto. " }, "serviceAccountEmptyProjectAccesspolicies": { "message": "Lägg till projekt för att bevilja åtkomst" @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Gick in i ett projekt med id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Redigerade ett projekt med identifieraren: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Tog bort ett projekt med identifieraren: $PROJECT_ID$", "placeholders": { @@ -8633,7 +8758,7 @@ "message": "Skapa ett projekt" }, "createServiceAccount": { - "message": "Skapa ett tjänstkonto" + "message": "Skapa ett tjänstekonto" }, "downloadThe": { "message": "Ladda ner", @@ -9437,6 +9562,9 @@ "assign": { "message": "Tilldela" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Tilldela till samlingar" }, diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 9552457b42a..e2426c2d69b 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "புதிய உள்நுழைவு உருப்படியை உருவாக்கவும்" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "அறிவிக்கப்பட்ட உறுப்பினர்கள் ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "ஆபத்தில் உள்ள உறுப்பினர்கள்" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "அறியப்படாத ரகசியம், இந்த ரகசியத்தை அணுகுவதற்கு நீங்கள் அனுமதி கோர வேண்டியிருக்கலாம்." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "அறியப்படாத திட்டம், இந்த திட்டத்தை அணுகுவதற்கு நீங்கள் அனுமதி கோர வேண்டியிருக்கலாம்." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "திட்டம் Id: $PROJECT_ID$ அணுகப்பட்டது.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "ஒரு திட்டத்தை திருத்தப்பட்டது: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "ஒரு திட்டத்தை நீக்கப்பட்டது: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "ஒதுக்கு" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "சேகரிப்புகளுக்கு ஒதுக்கு" }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 70a88883ec4..173520dab61 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index a55527f33a2..9a8958651a2 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Create new login item" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Assign" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 38fdd506966..6b19e0b5fe7 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Yeni hesap kaydı oluştur" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Kritik olarak işaretlediğiniz uygulamalar burada görünecektir" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Bildirilen üyeler ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Riskli üyeler" }, - "membersAtRiskActivityDescription": { - "message": "Kritik uygulamalar için risk altındaki kayıtlara düzenleme erişimi olan üyeler" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Kritik uygulamalar için risk altındaki kayıtlara erişimi olan üyeler" }, "membersAtRiskCount": { "message": "$COUNT$ üye risk altında", @@ -1500,7 +1543,7 @@ "message": "Geçersiz ana parola" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Ana parola geçersiz. E-posta adresinizin doğru olduğunu ve hesabınızın $HOST$ üzerinde oluşturulduğunu kontrol edin.", "placeholders": { "host": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Bilinmeyen sır, bu sırra erişmek için izin istemeniz gerekebilir." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Bilinmeyen proje, bu projeye erişmek için izin istemeniz gerekebilir." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Kimliği: $PROJECT_ID$ olan bir projeye erişildi.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Tanımlayıcısı: $PROJECT_ID$ olan bir proje düzenlendi", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Tanımlayıcısı: $PROJECT_ID$ olan bir proje silindi", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Ata" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Koleksiyonlara ata" }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 90cc5c77c61..db32311e2ae 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "Створити новий запис" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Сповіщення учасників ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "Ризиковані учасники" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Призначити" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Призначити до збірок" }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 2d87e120753..f5e3ee95bb6 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -59,17 +59,51 @@ "createNewLoginItem": { "message": "Tạo mục đăng nhập mới" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { - "message": "View at-risk members" + "message": "Xem các thành viên gặp rủi ro" }, "viewAtRiskApplications": { - "message": "View at-risk applications" + "message": "Xem các ứng dụng gặp rủi ro" }, "criticalApplicationsAreAtRisk": { - "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "message": "$COUNT$ trên tổng số $TOTAL$ ứng dụng quan trọng đang gặp rủi ro do mật khẩu không an toàn", "placeholders": { "count": { "content": "$1", @@ -100,7 +134,16 @@ } }, "countOfApplicationsAtRisk": { - "message": "$COUNT$ applications at-risk", + "message": "$COUNT$ ứng dụng gặp rủi ro", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", "placeholders": { "count": { "content": "$1", @@ -162,11 +205,11 @@ "atRiskMembers": { "message": "Các thành viên có rủi ro" }, - "membersAtRiskActivityDescription": { - "message": "Thành viên có quyền chỉnh sửa đối với các mục có nguy cơ cho các ứng dụng quan trọng" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { - "message": "$COUNT$ members at-risk", + "message": "$COUNT$ thành viên gặp rủi ro", "placeholders": { "count": { "content": "$1", @@ -1500,7 +1543,7 @@ "message": "Mật khẩu chính không hợp lệ" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Mật khẩu chính không hợp lệ. Xác nhận email của bạn là chính xác và tài khoản được tạo trên $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1518,28 +1561,28 @@ "message": "Chưa có mục nào." }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Không có mục nào trong thùng rác" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "Các mục bạn xóa sẽ hiển thị tại đây và bị xóa vĩnh viễn sau 30 ngày" }, "noItemsInVault": { - "message": "No items in the vault" + "message": "Không có mục nào trong kho lưu trữ" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "Kho lưu trữ không chỉ bảo vệ mật khẩu của bạn. Bạn có thể lưu trữ an toàn các thông tin đăng nhập, ID, thẻ và ghi chú tại đây." }, "emptyFavorites": { - "message": "You haven't favorited any items" + "message": "Bạn chưa đánh dấu mục nào là mục yêu thích" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "Thêm các mục thường dùng vào mục yêu thích để truy cập nhanh." }, "noSearchResults": { - "message": "No search results returned" + "message": "Không có kết quả tìm kiếm nào được trả về" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Xóa bộ lọc hoặc thử cụm từ tìm kiếm khác" }, "noPermissionToViewAllCollectionItems": { "message": "Bạn không có quyền xem tất cả mục trong bộ sưu tập này." @@ -4834,10 +4877,10 @@ "message": "Tổ chức đã bị tạm ngưng" }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "Tổ chức đã bị tạm dừng" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "Không thể truy cập các mục trong tổ chức đã bị tạm ngưng. Vui lòng liên hệ với chủ sở hữu tổ chức của bạn để được hỗ trợ." }, "secretsAccessSuspended": { "message": "Không thể truy cập các tổ chức đã bị tạm ngưng. Vui lòng liên hệ với chủ sở hữu tổ chức của bạn để được hỗ trợ." @@ -5225,11 +5268,11 @@ "message": "Mã định danh SSO" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "message": "Cung cấp ID này cho thành viên để đăng nhập bằng SSO. Thành viên có thể bỏ qua nhập định danh này trong SSO nếu một tên miền đã xác nhận được thiết lập. ", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "Tìm hiểu thêm", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -7044,7 +7087,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Chỉ kho lưu trữ tổ chức liên kết với $ORGANIZATION$ sẽ được xuất.", "placeholders": { "organization": { "content": "$1", @@ -7053,7 +7096,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Chỉ kho lưu trữ tổ chức liên kết với $ORGANIZATION$ được xuất. Bộ sưu tập mục của tôi sẽ không được bao gồm.", "placeholders": { "organization": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Bí mật không xác định, bạn có thể cần yêu cầu quyền để truy cập bí mật này." }, + "unknownServiceAccount": { + "message": "Tài khoản máy không xác định, bạn có thể cần yêu cầu quyền truy cập vào tài khoản máy này." + }, "unknownProject": { "message": "Dự án không xác định, bạn có thể cần yêu cầu quyền để truy cập dự án này." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Đã truy cập một dự án với ID: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Đã truy cập dự án với định danh: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Đã xóa Id tài khoản máy: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Đã chỉnh sửa một dự án với định danh: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Đã thêm người dùng: $USER_ID$ vào tài khoản máy với định danh: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Đã xóa người dùng: $USER_ID$ khỏi tài khoản máy với định danh: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Đã xóa nhóm: $GROUP_ID$ khỏi tài khoản máy với định danh: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Đã tạo tài khoản máy với định danh: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Đã thêm nhóm: $GROUP_ID$ vào tài khoản máy với định danh: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Đã tạo tài khoản máy với định danh: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Đã xóa một dự án với định danh: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "Gán" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Gán vào bộ sưu tập" }, @@ -9836,7 +9964,7 @@ "message": "Không thể lưu tích hợp. Vui lòng thử lại sau." }, "mustBeOrgOwnerToPerformAction": { - "message": "You must be the organization owner to perform this action." + "message": "Bạn phải là chủ sở hữu tổ chức để thực hiện hành động này." }, "failedToDeleteIntegration": { "message": "Không thể xóa tích hợp. Vui lòng thử lại sau." @@ -11102,11 +11230,11 @@ "message": "Tìm kiếm kho lưu trữ" }, "archiveNoun": { - "message": "Archive", + "message": "Lưu trữ", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Lưu trữ", "description": "Verb" }, "noItemsInArchive": { @@ -11223,10 +11351,10 @@ "message": "Đã cài đặt tiện ích mở rộng Bitwarden!" }, "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" + "message": "Mở tiện ích mở rộng Bitwarden" }, "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "message": "Tiện ích mở rộng Bitwarden đã được cài đặt! Mở tiện ích mở rộng để đăng nhập và bắt đầu tự động điền." }, "openExtensionToAutofill": { "message": "Mở tiện ích mở rộng để đăng nhập và bắt đầu tự động điền." diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 7acd946f616..12afbc9a33c 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "创建新的登录项目" }, + "percentageCompleted": { + "message": "$PERCENT$% 已完成", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "总计 $TOTAL$ 个中的 $COUNT$ 个安全任务已完成", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "密码修改进度" + }, + "assignMembersTasksToMonitorProgress": { + "message": "分配成员任务以监测进度" + }, + "onceYouReviewApplications": { + "message": "您审查应用程序并将其标记为关键后,它们将显示在这里。" + }, + "sendReminders": { + "message": "发送提醒" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "您将应用程序标记为关键后,它们将显示在这里" + "message": "您将应用程序标记为关键后,它们将显示在这里。" }, "viewAtRiskMembers": { "message": "查看存在风险的成员" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ 个密码存在风险", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "已通知的成员 ($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "存在风险的成员" }, - "membersAtRiskActivityDescription": { - "message": "对关键应用程序中存在风险的项目具有编辑权限的成员" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "对关键应用程序中存在风险的项目具有访问权限的成员" }, "membersAtRiskCount": { "message": "$COUNT$ 个成员存在风险", @@ -1500,7 +1543,7 @@ "message": "无效的主密码" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "无效的主密码。请确认您的电子邮箱正确无误,以及您的账户是在 $HOST$ 上创建的。", "placeholders": { "host": { "content": "$1", @@ -1518,28 +1561,28 @@ "message": "没有可列出的项目。" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "回收站中没有项目" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "您删除的项目将显示在这里,并在 30 天后永久删除" }, "noItemsInVault": { "message": "密码库中没有项目" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "密码库不仅保护您的密码。在这里还可以安全地存储登录、ID、支付卡和笔记。" }, "emptyFavorites": { - "message": "You haven't favorited any items" + "message": "您没有收藏任何项目" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "将常用项目添加到收藏夹以便快速访问。" }, "noSearchResults": { - "message": "No search results returned" + "message": "未返回搜索结果" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "清除筛选或尝试其他搜索词" }, "noPermissionToViewAllCollectionItems": { "message": "您没有查看此集合中的所有项目的权限。" @@ -4834,10 +4877,10 @@ "message": "组织已暂停" }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "组织已暂停" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "无法访问已暂停组织中的项目。请联系您的组织所有者寻求帮助。" }, "secretsAccessSuspended": { "message": "无法访问已暂停的组织。请联系您的组织所有者寻求帮助。" @@ -5225,11 +5268,11 @@ "message": "SSO 标识符" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "message": "请将此 ID 提供给您的成员以用于 SSO 登录。如果已设置声明域名,成员可以在 SSO 期间跳过输入此标识符。", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "进一步了解", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -7044,7 +7087,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库。", "placeholders": { "organization": { "content": "$1", @@ -7053,7 +7096,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库,不包括我的项目集合。", "placeholders": { "organization": { "content": "$1", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "未知的机密,您可能需要请求权限才能访问此机密。" }, + "unknownServiceAccount": { + "message": "未知的机器账户,您可能需要请求权限才能访问此机器账户。" + }, "unknownProject": { "message": "未知的工程,您可能需要请求权限才能访问此工程。" }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "访问了 ID 为 $PROJECT_ID$ 的工程。", + "accessedProjectWithIdentifier": { + "message": "访问了标识符为 $PROJECT_ID$ 的工程。", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "删除了 ID 为 $SERVICE_ACCOUNT_ID$ 的机器账户", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "编辑了标识符为 $PROJECT_ID$ 的工程", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "向标识符为 $SERVICE_ACCOUNT_ID$ 的机器账户添加了用户 $USER_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "从标识符为 $SERVICE_ACCOUNT_ID$ 的机器账户移除了用户 $USER_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "从标识符为 $SERVICE_ACCOUNT_ID$ 的机器账户移除了群组 $GROUP_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "创建了标识符为 $SERVICE_ACCOUNT_ID$ 的机器账户", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "向标识符为 $SERVICE_ACCOUNT_ID$ 的机器账户添加了群组 $GROUP_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "删除了标识符为 $SERVICE_ACCOUNT_ID$ 的机器账户", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "删除了标识符为 $PROJECT_ID$ 的工程", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "分配" }, + "assignTasks": { + "message": "分配任务" + }, "assignToCollections": { "message": "分配到集合" }, @@ -11102,11 +11230,11 @@ "message": "搜索归档" }, "archiveNoun": { - "message": "Archive", + "message": "归档", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "归档", "description": "Verb" }, "noItemsInArchive": { @@ -11223,10 +11351,10 @@ "message": "Bitwarden 扩展已安装!" }, "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" + "message": "打开 Bitwarden 扩展" }, "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "message": "Bitwarden 扩展已安装!打开扩展以登录并开始自动填充。" }, "openExtensionToAutofill": { "message": "打开扩展以登录并开始自动填充。" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index bee3c8ea76a..15c05a9598c 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -59,8 +59,42 @@ "createNewLoginItem": { "message": "新增登入項目" }, + "percentageCompleted": { + "message": "$PERCENT$% complete", + "placeholders": { + "percent": { + "content": "$1", + "example": "75" + } + } + }, + "securityTasksCompleted": { + "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + }, + "total": { + "content": "$2", + "example": "5" + } + } + }, + "passwordChangeProgress": { + "message": "Password change progress" + }, + "assignMembersTasksToMonitorProgress": { + "message": "Assign members tasks to monitor progress" + }, + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, they will display here." + }, + "sendReminders": { + "message": "Send reminders" + }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here" + "message": "Once you mark applications critical, they will display here." }, "viewAtRiskMembers": { "message": "View at-risk members" @@ -108,6 +142,15 @@ } } }, + "countOfAtRiskPasswords": { + "message": "$COUNT$ passwords at-risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "已被通知的成員($COUNT$)", "placeholders": { @@ -162,8 +205,8 @@ "atRiskMembers": { "message": "具有風險的成員" }, - "membersAtRiskActivityDescription": { - "message": "Members with edit access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApps": { + "message": "Members with access to at-risk items for critical applications" }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -7215,6 +7258,9 @@ "unknownSecret": { "message": "Unknown secret, you may need to request permission to access this secret." }, + "unknownServiceAccount": { + "message": "Unknown machine account, you may need to request permission to access this machine account." + }, "unknownProject": { "message": "Unknown project, you may need to request permission to access this project." }, @@ -8565,8 +8611,8 @@ } } }, - "accessedProjectWithId": { - "message": "Accessed a project with Id: $PROJECT_ID$.", + "accessedProjectWithIdentifier": { + "message": "Accessed a project with identifier: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8592,6 +8638,15 @@ } } }, + "nameUnavailableServiceAccountDeleted": { + "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "editedProjectWithId": { "message": "Edited a project with identifier: $PROJECT_ID$", "placeholders": { @@ -8601,6 +8656,76 @@ } } }, + "addedUserToServiceAccountWithId": { + "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedUserToServiceAccountWithId": { + "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "user_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "removedGroupFromServiceAccountWithId": { + "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountCreatedWithId": { + "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, + "addedGroupToServiceAccountId": { + "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "group_id": { + "content": "$1", + "example": "4d34e8a8" + }, + "service_account_id": { + "content": "$2", + "example": "4d34e8a8" + } + } + }, + "serviceAccountDeletedWithId": { + "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "placeholders": { + "service_account_id": { + "content": "$1", + "example": "4d34e8a8" + } + } + }, "deletedProjectWithId": { "message": "Deleted a project with identifier: $PROJECT_ID$", "placeholders": { @@ -9437,6 +9562,9 @@ "assign": { "message": "指派" }, + "assignTasks": { + "message": "Assign tasks" + }, "assignToCollections": { "message": "Assign to collections" }, From bbb49c245b36a7b5f8be4b737e0e59b57ff60f23 Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Mon, 6 Oct 2025 10:44:48 +0000 Subject: [PATCH 64/83] Bumped client version(s) --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- apps/cli/package.json | 2 +- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- apps/web/package.json | 2 +- package-lock.json | 8 ++++---- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 24a53f43f66..402a00fee31 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.9.0", + "version": "2025.10.0", "scripts": { "build": "npm run build:chrome", "build:bit": "npm run build:bit:chrome", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 2a45c846060..ee1656d5b68 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.9.0", + "version": "2025.10.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 6eeac8c8b39..dc773f91fd1 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.9.0", + "version": "2025.10.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/cli/package.json b/apps/cli/package.json index 659a68d13a5..02db5317a26 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2025.9.0", + "version": "2025.10.0", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index fc3e7d0cad3..cb997273f1e 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.9.1", + "version": "2025.10.0", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 706ad67aa2a..8db495f69c8 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.9.1", + "version": "2025.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.9.1", + "version": "2025.10.0", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 7d8d0cc18a2..c4b4992e754 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.9.1", + "version": "2025.10.0", "author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/web/package.json b/apps/web/package.json index 517b8aa8004..5690ce77dec 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.9.1", + "version": "2025.10.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 a955d12294e..afcedcfaf74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -191,11 +191,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.9.0" + "version": "2025.10.0" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2025.9.0", + "version": "2025.10.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "4.0.0", @@ -277,7 +277,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.9.1", + "version": "2025.10.0", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -291,7 +291,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.9.1" + "version": "2025.10.0" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From 525a6003bcc622af311eb926204a41ddbfff083b Mon Sep 17 00:00:00 2001 From: Mick Letofsky <mletofsky@bitwarden.com> Date: Mon, 6 Oct 2025 14:32:06 +0200 Subject: [PATCH 65/83] Create Claude code review action (#16745) --- .github/workflows/review-code.yml | 109 ++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 .github/workflows/review-code.yml diff --git a/.github/workflows/review-code.yml b/.github/workflows/review-code.yml new file mode 100644 index 00000000000..b49f5cec8f0 --- /dev/null +++ b/.github/workflows/review-code.yml @@ -0,0 +1,109 @@ +name: Review code + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: {} + +jobs: + review: + name: Review + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write + pull-requests: write + + steps: + - name: Check out repo + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Check for Vault team changes + id: check_changes + run: | + # Ensure we have the base branch + git fetch origin ${{ github.base_ref }} + + echo "Comparing changes between origin/${{ github.base_ref }} and HEAD" + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + + if [ -z "$CHANGED_FILES" ]; then + echo "Zero files changed" + echo "vault_team_changes=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Handle variations in spacing and multiple teams + VAULT_PATTERNS=$(grep -E "@bitwarden/team-vault-dev(\s|$)" .github/CODEOWNERS 2>/dev/null | awk '{print $1}') + + if [ -z "$VAULT_PATTERNS" ]; then + echo "⚠️ No patterns found for @bitwarden/team-vault-dev in CODEOWNERS" + echo "vault_team_changes=false" >> $GITHUB_OUTPUT + exit 0 + fi + + vault_team_changes=false + for pattern in $VAULT_PATTERNS; do + echo "Checking pattern: $pattern" + + # Handle **/directory patterns + if [[ "$pattern" == "**/"* ]]; then + # Remove the **/ prefix + dir_pattern="${pattern#\*\*/}" + # Check if any file contains this directory in its path + if echo "$CHANGED_FILES" | grep -qE "(^|/)${dir_pattern}(/|$)"; then + vault_team_changes=true + echo "✅ Found files matching pattern: $pattern" + echo "$CHANGED_FILES" | grep -E "(^|/)${dir_pattern}(/|$)" | sed 's/^/ - /' + break + fi + else + # Handle other patterns (shouldn't happen based on your CODEOWNERS) + if echo "$CHANGED_FILES" | grep -q "$pattern"; then + vault_team_changes=true + echo "✅ Found files matching pattern: $pattern" + echo "$CHANGED_FILES" | grep "$pattern" | sed 's/^/ - /' + break + fi + fi + done + + echo "vault_team_changes=$vault_team_changes" >> $GITHUB_OUTPUT + + if [ "$vault_team_changes" = "true" ]; then + echo "" + echo "✅ Vault team changes detected - proceeding with review" + else + echo "" + echo "❌ No Vault team changes detected - skipping review" + fi + + - name: Review with Claude Code + if: steps.check_changes.outputs.vault_team_changes == 'true' + uses: anthropics/claude-code-action@a5528eec7426a4f0c9c1ac96018daa53ebd05bc4 # v1.0.7 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + track_progress: true + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + TITLE: ${{ github.event.pull_request.title }} + BODY: ${{ github.event.pull_request.body }} + AUTHOR: ${{ github.event.pull_request.user.login }} + + Please review this pull request with a focus on: + - Code quality and best practices + - Potential bugs or issues + - Security implications + - Performance considerations + + Note: The PR branch is already checked out in the current working directory. + + Provide detailed feedback using inline comments for specific issues. + + claude_args: | + --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" From 6cbdecef43065798e6855d85ce72a9df1416c4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= <anders@andersaberg.com> Date: Mon, 6 Oct 2025 15:25:51 +0200 Subject: [PATCH 66/83] PM-13632: Enable sign in with passkeys in the browser extension for chromium browsers (#16385) * PM-13632: Enable sign in with passkeys in the browser extension * Refactor component + Icon fix This commit refactors the login-via-webauthn commit as per @JaredSnider-Bitwarden suggestions. It also fixes an existing issue where Icons are not displayed properly on the web vault. Remove old one. Rename the file Working refactor Removed the icon from the component Fixed icons not showing. Changed layout to be 'embedded' * Add tracking links * Update app.module.ts * Remove default Icons on load * Remove login.module.ts * Add env changer to the passkey component * Remove leftover dependencies * use .isChromium() --- apps/browser/src/_locales/en/messages.json | 9 +++ .../extension-login-component.service.ts | 14 ++++ apps/browser/src/popup/app-routing.module.ts | 25 +++++++ .../login-via-webauthn.component.html | 54 -------------- .../login-via-webauthn.component.ts | 19 ----- apps/web/src/app/auth/login/login.module.ts | 14 ---- apps/web/src/app/oss-routing.module.ts | 30 ++++++-- apps/web/src/app/oss.module.ts | 3 - apps/web/src/locales/en/messages.json | 3 + .../login-via-webauthn.component.html | 20 +++++ .../login-via-webauthn.component.ts} | 73 +++++++++++++++++-- 11 files changed, 162 insertions(+), 102 deletions(-) delete mode 100644 apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.html delete mode 100644 apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts delete mode 100644 apps/web/src/app/auth/login/login.module.ts create mode 100644 libs/angular/src/auth/login-via-webauthn/login-via-webauthn.component.html rename libs/angular/src/auth/{components/base-login-via-webauthn.component.ts => login-via-webauthn/login-via-webauthn.component.ts} (62%) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index df47d357746..d91a33c6796 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1548,6 +1548,15 @@ "readSecurityKey": { "message": "Read security key" }, + "readingPasskeyLoading": { + "message": "Reading passkey..." + }, + "passkeyAuthenticationFailed": { + "message": "Passkey authentication failed" + }, + "useADifferentLogInMethod": { + "message": "Use a different log in method" + }, "awaitingSecurityKeyInteraction": { "message": "Awaiting security key interaction..." }, diff --git a/apps/browser/src/auth/popup/login/extension-login-component.service.ts b/apps/browser/src/auth/popup/login/extension-login-component.service.ts index 37d74616391..621c7d74876 100644 --- a/apps/browser/src/auth/popup/login/extension-login-component.service.ts +++ b/apps/browser/src/auth/popup/login/extension-login-component.service.ts @@ -68,4 +68,18 @@ export class ExtensionLoginComponentService showBackButton(showBackButton: boolean): void { this.extensionAnonLayoutWrapperDataService.setAnonLayoutWrapperData({ showBackButton }); } + + /** + * Enable passkey login support for chromium-based browsers only. + * Neither Firefox nor safari support overriding the relying party ID in an extension. + * + * https://github.com/w3c/webextensions/issues/238 + * + * Tracking links: + * https://bugzilla.mozilla.org/show_bug.cgi?id=1956484 + * https://developer.apple.com/forums/thread/774351 + */ + isLoginWithPasskeySupported(): boolean { + return this.platformUtilsService.isChromium(); + } } diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index b69d7b73672..17a812f451c 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -12,6 +12,7 @@ import { tdeDecryptionRequiredGuard, unauthGuardFn, } from "@bitwarden/angular/auth/guards"; +import { LoginViaWebAuthnComponent } from "@bitwarden/angular/auth/login-via-webauthn/login-via-webauthn.component"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component"; import { @@ -22,6 +23,7 @@ import { UserLockIcon, VaultIcon, LockIcon, + TwoFactorAuthSecurityKeyIcon, DeactivatedOrg, } from "@bitwarden/assets/svg"; import { @@ -403,6 +405,29 @@ const routes: Routes = [ }, ], }, + { + path: "login-with-passkey", + canActivate: [unauthGuardFn(unauthRouteOverrides)], + data: { + pageIcon: TwoFactorAuthSecurityKeyIcon, + pageTitle: { + key: "logInWithPasskey", + }, + pageSubtitle: { + key: "readingPasskeyLoadingInfo", + }, + elevation: 1, + showBackButton: true, + } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, + children: [ + { path: "", component: LoginViaWebAuthnComponent }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, { path: "sso", canActivate: [unauthGuardFn(unauthRouteOverrides)], diff --git a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.html b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.html deleted file mode 100644 index 94dfac42976..00000000000 --- a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.html +++ /dev/null @@ -1,54 +0,0 @@ -<div - class="tw-mx-auto tw-mt-5 tw-flex tw-max-w-lg tw-flex-col tw-items-center tw-justify-center tw-p-8" -> - <div> - <img class="logo logo-themed" alt="Bitwarden" /> - <h3 bitTypography="h3" class="tw-my-8 tw-mb-3 tw-text-center"> - {{ "readingPasskeyLoading" | i18n }} - </h3> - - <div - class="tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6" - > - <div class="tw-flex tw-flex-col tw-items-center"> - <ng-container *ngIf="currentState === 'assert'"> - <div class="tw-size-24 tw-content-center tw-my-10"> - <bit-icon [icon]="Icons.TwoFactorAuthSecurityKeyIcon"></bit-icon> - </div> - <p bitTypography="body1">{{ "readingPasskeyLoadingInfo" | i18n }}</p> - <button - type="button" - bitButton - block - [loading]="true" - buttonType="primary" - class="tw-mb-4" - > - {{ "loading" | i18n }} - </button> - </ng-container> - - <ng-container *ngIf="currentState === 'assertFailed'"> - <div class="tw-size-24 tw-content-center tw-my-10"> - <bit-icon [icon]="Icons.TwoFactorAuthSecurityKeyFailedIcon"></bit-icon> - </div> - <p bitTypography="body1">{{ "readingPasskeyLoadingInfo" | i18n }}</p> - <button - type="button" - bitButton - block - buttonType="primary" - class="tw-mb-4" - (click)="retry()" - > - {{ "tryAgain" | i18n }} - </button> - </ng-container> - </div> - <p bitTypography="body1" class="tw-mb-0"> - {{ "troubleLoggingIn" | i18n }}<br /> - <a bitLink routerLink="/login">{{ "useADifferentLogInMethod" | i18n }}</a> - </p> - </div> - </div> -</div> diff --git a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts deleted file mode 100644 index 695e935b919..00000000000 --- a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component } from "@angular/core"; - -import { BaseLoginViaWebAuthnComponent } from "@bitwarden/angular/auth/components/base-login-via-webauthn.component"; -import { - TwoFactorAuthSecurityKeyIcon, - TwoFactorAuthSecurityKeyFailedIcon, -} from "@bitwarden/assets/svg"; - -@Component({ - selector: "app-login-via-webauthn", - templateUrl: "login-via-webauthn.component.html", - standalone: false, -}) -export class LoginViaWebAuthnComponent extends BaseLoginViaWebAuthnComponent { - protected readonly Icons = { - TwoFactorAuthSecurityKeyIcon, - TwoFactorAuthSecurityKeyFailedIcon, - }; -} diff --git a/apps/web/src/app/auth/login/login.module.ts b/apps/web/src/app/auth/login/login.module.ts deleted file mode 100644 index 9a99c84f727..00000000000 --- a/apps/web/src/app/auth/login/login.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { CheckboxModule } from "@bitwarden/components"; - -import { SharedModule } from "../../../app/shared"; - -import { LoginViaWebAuthnComponent } from "./login-via-webauthn/login-via-webauthn.component"; - -@NgModule({ - imports: [SharedModule, CheckboxModule], - declarations: [LoginViaWebAuthnComponent], - exports: [LoginViaWebAuthnComponent], -}) -export class LoginModule {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 9ac628752b6..7ffe69b7ee6 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -10,6 +10,7 @@ import { unauthGuardFn, activeAuthGuard, } from "@bitwarden/angular/auth/guards"; +import { LoginViaWebAuthnComponent } from "@bitwarden/angular/auth/login-via-webauthn/login-via-webauthn.component"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component"; import { @@ -17,6 +18,7 @@ import { RegistrationUserAddIcon, TwoFactorTimeoutIcon, TwoFactorAuthEmailIcon, + TwoFactorAuthSecurityKeyIcon, UserLockIcon, VaultIcon, SsoKeyIcon, @@ -49,7 +51,6 @@ import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/ import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component"; import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; import { deepLinkGuard } from "./auth/guards/deep-link/deep-link.guard"; -import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login-via-webauthn.component"; import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component"; import { RecoverDeleteComponent } from "./auth/recover-delete.component"; import { RecoverTwoFactorComponent } from "./auth/recover-two-factor.component"; @@ -106,11 +107,6 @@ const routes: Routes = [ children: [], // Children lets us have an empty component. canActivate: [redirectGuard()], // Redirects either to vault, login, or lock page. }, - { - path: "login-with-passkey", - component: LoginViaWebAuthnComponent, - data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties, - }, { path: "verify-email", component: VerifyEmailTokenComponent }, { path: "accept-organization", @@ -140,6 +136,28 @@ const routes: Routes = [ path: "", component: AnonLayoutWrapperComponent, children: [ + { + path: "login-with-passkey", + canActivate: [unauthGuardFn()], + data: { + pageIcon: TwoFactorAuthSecurityKeyIcon, + titleId: "logInWithPasskey", + pageTitle: { + key: "logInWithPasskey", + }, + pageSubtitle: { + key: "readingPasskeyLoadingInfo", + }, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + children: [ + { path: "", component: LoginViaWebAuthnComponent }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, { path: "signup", canActivate: [unauthGuardFn()], diff --git a/apps/web/src/app/oss.module.ts b/apps/web/src/app/oss.module.ts index 4e04910246f..ce1b45a9e47 100644 --- a/apps/web/src/app/oss.module.ts +++ b/apps/web/src/app/oss.module.ts @@ -1,7 +1,6 @@ import { NgModule } from "@angular/core"; import { AuthModule } from "./auth"; -import { LoginModule } from "./auth/login/login.module"; import { TrialInitiationModule } from "./billing/trial-initiation/trial-initiation.module"; import { HeaderModule } from "./layouts/header/header.module"; import { SharedModule } from "./shared"; @@ -21,7 +20,6 @@ import "./shared/locales"; TrialInitiationModule, VaultFilterModule, OrganizationBadgeModule, - LoginModule, AuthModule, AccessComponent, ], @@ -31,7 +29,6 @@ import "./shared/locales"; TrialInitiationModule, VaultFilterModule, OrganizationBadgeModule, - LoginModule, AccessComponent, ], bootstrap: [], diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 5ed393c0295..7f08d3f02d1 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1248,6 +1248,9 @@ "readingPasskeyLoadingInfo": { "message": "Keep this window open and follow prompts from your browser." }, + "passkeyAuthenticationFailed": { + "message": "Passkey authentication failed. Please try again." + }, "useADifferentLogInMethod": { "message": "Use a different log in method" }, diff --git a/libs/angular/src/auth/login-via-webauthn/login-via-webauthn.component.html b/libs/angular/src/auth/login-via-webauthn/login-via-webauthn.component.html new file mode 100644 index 00000000000..1fe0f18ceb7 --- /dev/null +++ b/libs/angular/src/auth/login-via-webauthn/login-via-webauthn.component.html @@ -0,0 +1,20 @@ +<div class="tw-flex tw-flex-col tw-items-center"> + <ng-container *ngIf="currentState === 'assert'"> + <p bitTypography="body1" class="tw-text-center">{{ "readingPasskeyLoading" | i18n }}</p> + <button type="button" bitButton block [loading]="true" buttonType="primary" class="tw-mb-4"> + {{ "loading" | i18n }} + </button> + </ng-container> + + <ng-container *ngIf="currentState === 'assertFailed'"> + <p bitTypography="body1" class="tw-text-center">{{ "passkeyAuthenticationFailed" | i18n }}</p> + <button type="button" bitButton block buttonType="primary" class="tw-mb-4" (click)="retry()"> + {{ "tryAgain" | i18n }} + </button> + </ng-container> + + <p bitTypography="body1" class="tw-mb-0 tw-text-center"> + {{ "troubleLoggingIn" | i18n }}<br /> + <a bitLink routerLink="/login">{{ "useADifferentLogInMethod" | i18n }}</a> + </p> +</div> diff --git a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts b/libs/angular/src/auth/login-via-webauthn/login-via-webauthn.component.ts similarity index 62% rename from libs/angular/src/auth/components/base-login-via-webauthn.component.ts rename to libs/angular/src/auth/login-via-webauthn/login-via-webauthn.component.ts index 53e29d4d940..f795b66d916 100644 --- a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts +++ b/libs/angular/src/auth/login-via-webauthn/login-via-webauthn.component.ts @@ -1,27 +1,69 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Directive, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; +import { Router, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + TwoFactorAuthSecurityKeyIcon, + TwoFactorAuthSecurityKeyFailedIcon, +} from "@bitwarden/assets/svg"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { LoginSuccessHandlerService } from "@bitwarden/auth/common"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view"; +import { ClientType } from "@bitwarden/common/enums"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +import { + AnonLayoutWrapperDataService, + ButtonModule, + IconModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; export type State = "assert" | "assertFailed"; - -@Directive() -export class BaseLoginViaWebAuthnComponent implements OnInit { +@Component({ + selector: "app-login-via-webauthn", + templateUrl: "login-via-webauthn.component.html", + standalone: true, + imports: [ + CommonModule, + RouterModule, + JslibModule, + ButtonModule, + IconModule, + LinkModule, + TypographyModule, + ], +}) +export class LoginViaWebAuthnComponent implements OnInit { protected currentState: State = "assert"; - protected successRoute = "/vault"; + protected readonly Icons = { + TwoFactorAuthSecurityKeyIcon, + TwoFactorAuthSecurityKeyFailedIcon, + }; + + private readonly successRoutes: Record<ClientType, string> = { + [ClientType.Web]: "/vault", + [ClientType.Browser]: "/tabs/vault", + [ClientType.Desktop]: "/vault", + [ClientType.Cli]: "/vault", + }; + + protected get successRoute(): string { + const clientType = this.platformUtilsService.getClientType(); + return this.successRoutes[clientType] || "/vault"; + } constructor( private webAuthnLoginService: WebAuthnLoginServiceAbstraction, @@ -31,6 +73,8 @@ export class BaseLoginViaWebAuthnComponent implements OnInit { private i18nService: I18nService, private loginSuccessHandlerService: LoginSuccessHandlerService, private keyService: KeyService, + private platformUtilsService: PlatformUtilsService, + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, ) {} ngOnInit(): void { @@ -41,6 +85,8 @@ export class BaseLoginViaWebAuthnComponent implements OnInit { protected retry() { this.currentState = "assert"; + // Reset to default icon on retry + this.setDefaultIcon(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.authenticate(); @@ -54,6 +100,7 @@ export class BaseLoginViaWebAuthnComponent implements OnInit { } catch (error) { this.validationService.showError(error); this.currentState = "assertFailed"; + this.setFailureIcon(); return; } try { @@ -64,6 +111,7 @@ export class BaseLoginViaWebAuthnComponent implements OnInit { this.i18nService.t("twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn"), ); this.currentState = "assertFailed"; + this.setFailureIcon(); return; } @@ -80,6 +128,19 @@ export class BaseLoginViaWebAuthnComponent implements OnInit { } this.logService.error(error); this.currentState = "assertFailed"; + this.setFailureIcon(); } } + + private setDefaultIcon(): void { + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageIcon: this.Icons.TwoFactorAuthSecurityKeyIcon, + }); + } + + private setFailureIcon(): void { + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageIcon: this.Icons.TwoFactorAuthSecurityKeyFailedIcon, + }); + } } From 2ce194c190cdd497103088a699cc40c252cd523f Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Mon, 6 Oct 2025 10:48:03 -0400 Subject: [PATCH 67/83] [PM-14236] most recently used login is no longer moved to top of the list (#16388) * PM-14236 add overlay background call to internal autofill * update overlay background to handel vaultAutofillSuggestionUsed call * update main and runtime to pass message * add rough testing to verify calls are being made or not * remove spacing * reduce scope and handle update in main background * clean type, remove cipherId which is no longer used * when keyboard shortcut is used, update overlay ciphers to freflect new order immediately * keep separation of concerns, put handleAutofillSuggestionUsed back in overlay, add tests * reduced approach --- .../src/background/runtime.background.ts | 1 + .../vault-popup-autofill.service.spec.ts | 23 +++++++++++++++++++ .../services/vault-popup-autofill.service.ts | 17 ++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index d7aef0db375..9dc2bff65e5 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -145,6 +145,7 @@ export default class RuntimeBackground { if (totpCode != null) { this.platformUtilsService.copyToClipboard(totpCode); } + await this.main.updateOverlayCiphers(); break; } case ExtensionCommand.AutofillCard: { diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index f271b255c3e..718043b4e85 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -204,6 +204,7 @@ describe("VaultPopupAutofillService", () => { describe("doAutofill()", () => { it("should return true if autofill is successful", async () => { + mockCipher.id = "test-cipher-id"; mockAutofillService.doAutoFill.mockResolvedValue(null); const result = await service.doAutofill(mockCipher); expect(result).toBe(true); @@ -251,6 +252,7 @@ describe("VaultPopupAutofillService", () => { }); it("should copy TOTP code to clipboard if available", async () => { + mockCipher.id = "test-cipher-id-with-totp"; const totpCode = "123456"; mockAutofillService.doAutoFill.mockResolvedValue(totpCode); await service.doAutofill(mockCipher); @@ -405,5 +407,26 @@ describe("VaultPopupAutofillService", () => { }); }); }); + describe("handleAutofillSuggestionUsed", () => { + const cipherId = "cipher-123"; + + beforeEach(() => { + mockCipherService.updateLastUsedDate.mockResolvedValue(undefined); + }); + + it("updates last used date when there is an active user", async () => { + await service.handleAutofillSuggestionUsed({ cipherId }); + + expect(mockCipherService.updateLastUsedDate).toHaveBeenCalledTimes(1); + expect(mockCipherService.updateLastUsedDate).toHaveBeenCalledWith(cipherId, mockUserId); + }); + + it("does nothing when there is no active user", async () => { + accountService.activeAccount$ = of(null); + await service.handleAutofillSuggestionUsed({ cipherId }); + + expect(mockCipherService.updateLastUsedDate).not.toHaveBeenCalled(); + }); + }); }); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index 2d30e857573..3d5b35cded6 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -16,6 +16,7 @@ import { } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { isUrlInList } from "@bitwarden/common/autofill/utils"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -268,6 +269,7 @@ export class VaultPopupAutofillService { }); return false; } + await this.handleAutofillSuggestionUsed({ cipherId: cipher.id }); return true; } @@ -326,6 +328,21 @@ export class VaultPopupAutofillService { return didAutofill; } + /** + * When a user autofills with an autofill suggestion outside of the inline menu, + * update the cipher's last used date. + * + * @param message - The message containing the cipher ID that was used + */ + async handleAutofillSuggestionUsed(message: { cipherId: string }) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId) { + await this.cipherService.updateLastUsedDate(message.cipherId, activeUserId); + } + } + /** * Attempts to autofill the given cipher and, upon successful autofill, saves the URI to the cipher. * Will copy any TOTP code to the clipboard if available after successful autofill. From 8cf379d997e2a0658a122f71e787f2a8f0b0f521 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:39:40 +0200 Subject: [PATCH 68/83] [PM-22305] Upgrade typescript to 5.8 (#15044) Upgrade to the latest supported typescript version in Angular. Resolved TS errors by: - adding `: any` which is what the compiler previously implied and now warns about. - adding `toJSON` to satisfy requirement. --- .../services/account-switcher.service.ts | 2 +- .../src/autofill/services/autofill.service.ts | 2 +- .../popup/services/browser-router.service.ts | 4 +-- .../view-cache/popup-router-cache.service.ts | 4 +-- .../vault-popup-list-filters.service.spec.ts | 12 ++++--- .../src/vault/app/vault/vault-v2.component.ts | 10 +++--- .../services/subscription-pricing.service.ts | 32 ++++++++++--------- apps/web/src/app/core/router.service.ts | 4 +-- .../shared/product-switcher.service.ts | 2 +- .../vault-items/vault-items.component.ts | 3 +- .../project/project-people.component.ts | 2 +- .../webauthn-login.strategy.spec.ts | 6 +++- .../webauthn-login.service.spec.ts | 6 +++- .../src/vault/utils/observable-utilities.ts | 2 +- ...ditional-options-section.component.spec.ts | 2 +- .../autofill-options.component.spec.ts | 2 +- .../card-details-section.component.spec.ts | 2 +- .../components/cipher-form.component.spec.ts | 2 +- .../login-details-section.component.spec.ts | 2 +- package-lock.json | 9 +++--- package.json | 2 +- 21 files changed, 60 insertions(+), 52 deletions(-) diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts index 7bb12fc260d..99d2c83283e 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts @@ -160,7 +160,7 @@ export class AccountSwitcherService { throwError(() => new Error(AccountSwitcherService.incompleteAccountSwitchError)), }), ), - ).catch((err) => { + ).catch((err): any => { if ( err instanceof Error && err.message === AccountSwitcherService.incompleteAccountSwitchError diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 0e238d14d23..ca735f8f4f3 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -161,7 +161,7 @@ export default class AutofillService implements AutofillServiceInterface { // Create a timeout observable that emits an empty array if pageDetailsFromTab$ hasn't emitted within 1 second. const pageDetailsTimeout$ = timer(1000).pipe( - map(() => []), + map((): any => []), takeUntil(sharedPageDetailsFromTab$), ); diff --git a/apps/browser/src/platform/popup/services/browser-router.service.ts b/apps/browser/src/platform/popup/services/browser-router.service.ts index 2d449b8a0f2..e1de1fdd29d 100644 --- a/apps/browser/src/platform/popup/services/browser-router.service.ts +++ b/apps/browser/src/platform/popup/services/browser-router.service.ts @@ -21,9 +21,7 @@ export class BrowserRouterService { child = child.firstChild; } - // TODO: Eslint upgrade. Please resolve this since the ?? does nothing - // eslint-disable-next-line no-constant-binary-expression - const updateUrl = !child?.data?.doNotSaveUrl ?? true; + const updateUrl = !child?.data?.doNotSaveUrl; if (updateUrl) { this.setPreviousUrl(event.url); diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts index b545618c0ce..2e9746642f4 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts @@ -62,9 +62,7 @@ export class PopupRouterCacheService { child = child.firstChild; } - // TODO: Eslint upgrade. Please resolve this since the ?? does nothing - // eslint-disable-next-line no-constant-binary-expression - return !child?.data?.doNotSaveUrl ?? true; + return !child?.data?.doNotSaveUrl; }), switchMap((event) => this.push(event.url)), ) diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index eecd1f2fd68..692e21d0084 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -761,11 +761,13 @@ function createSeededVaultPopupListFiltersService( const collectionServiceMock = { decryptedCollections$: () => seededCollections$, getAllNested: () => - seededCollections$.value.map((c) => ({ - children: [], - node: c, - parent: null, - })), + seededCollections$.value.map( + (c): TreeNode<CollectionView> => ({ + children: [], + node: c, + parent: null as any, + }), + ), } as any; const folderServiceMock = { diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 3fdb14aa154..aa631c44c64 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -290,7 +290,7 @@ export class VaultV2Component<C extends CipherViewLike> ) { const value = await firstValueFrom( this.totpService.getCode$(this.cipher.login.totp), - ).catch(() => null); + ).catch((): any => null); if (value) { this.copyValue(this.cipher, value.code, "verificationCodeTotp", "TOTP"); } @@ -329,7 +329,7 @@ export class VaultV2Component<C extends CipherViewLike> this.activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getUserId), - ).catch(() => null); + ).catch((): any => null); if (this.activeUserId) { this.cipherService @@ -448,7 +448,7 @@ export class VaultV2Component<C extends CipherViewLike> const dialogRef = AttachmentsV2Component.open(this.dialogService, { cipherId: this.cipherId as CipherId, }); - const result = await firstValueFrom(dialogRef.closed).catch(() => null); + const result = await firstValueFrom(dialogRef.closed).catch((): any => null); if ( result?.action === AttachmentDialogResult.Removed || result?.action === AttachmentDialogResult.Uploaded @@ -574,7 +574,7 @@ export class VaultV2Component<C extends CipherViewLike> click: async () => { const value = await firstValueFrom( this.totpService.getCode$(cipher.login.totp), - ).catch(() => null); + ).catch((): any => null); if (value) { this.copyValue(cipher, value.code, "verificationCodeTotp", "TOTP"); } @@ -617,7 +617,7 @@ export class VaultV2Component<C extends CipherViewLike> async buildFormConfig(action: CipherFormMode) { this.config = await this.formConfigService .buildConfig(action, this.cipherId as CipherId, this.addType) - .catch(() => null); + .catch((): any => null); } async editCipher(cipher: CipherView) { diff --git a/apps/web/src/app/billing/services/subscription-pricing.service.ts b/apps/web/src/app/billing/services/subscription-pricing.service.ts index fad797bed51..82ec9f180b9 100644 --- a/apps/web/src/app/billing/services/subscription-pricing.service.ts +++ b/apps/web/src/app/billing/services/subscription-pricing.service.ts @@ -110,7 +110,7 @@ export class SubscriptionPricingService { ); private free$: Observable<BusinessSubscriptionPricingTier> = this.plansResponse$.pipe( - map((plans) => { + map((plans): BusinessSubscriptionPricingTier => { const freePlan = plans.data.find((plan) => plan.type === PlanType.Free)!; return { @@ -215,20 +215,22 @@ export class SubscriptionPricingService { ); private custom$: Observable<BusinessSubscriptionPricingTier> = this.plansResponse$.pipe( - map(() => ({ - id: BusinessSubscriptionPricingTierIds.Custom, - name: this.i18nService.t("planNameCustom"), - description: this.i18nService.t("planDescCustom"), - availableCadences: [], - passwordManager: { - type: "custom", - features: [ - this.featureTranslations.strengthenCybersecurity(), - this.featureTranslations.boostProductivity(), - this.featureTranslations.seamlessIntegration(), - ], - }, - })), + map( + (): BusinessSubscriptionPricingTier => ({ + id: BusinessSubscriptionPricingTierIds.Custom, + name: this.i18nService.t("planNameCustom"), + description: this.i18nService.t("planDescCustom"), + availableCadences: [], + passwordManager: { + type: "custom", + features: [ + this.featureTranslations.strengthenCybersecurity(), + this.featureTranslations.boostProductivity(), + this.featureTranslations.seamlessIntegration(), + ], + }, + }), + ), ); private showUnexpectedErrorToast() { diff --git a/apps/web/src/app/core/router.service.ts b/apps/web/src/app/core/router.service.ts index 603c171e95b..7a2e53a374e 100644 --- a/apps/web/src/app/core/router.service.ts +++ b/apps/web/src/app/core/router.service.ts @@ -75,9 +75,7 @@ export class RouterService { const titleId: string = child?.snapshot?.data?.titleId; const rawTitle: string = child?.snapshot?.data?.title; - // TODO: Eslint upgrade. Please resolve this since the ?? does nothing - // eslint-disable-next-line no-constant-binary-expression - const updateUrl = !child?.snapshot?.data?.doNotSaveUrl ?? true; + const updateUrl = !child?.snapshot?.data?.doNotSaveUrl; if (titleId != null || rawTitle != null) { const newTitle = rawTitle != null ? rawTitle : i18nService.t(titleId); diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index ba86e9fed28..95acf4447e9 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -87,7 +87,7 @@ export class ProductSwitcherService { startWith(null), // Start with a null event to trigger the initial combineLatest filter((e) => e instanceof NavigationEnd || e instanceof NavigationStart || e === null), ), - ]).pipe(map(() => null)); + ]).pipe(map((): any => null)); constructor( private organizationService: OrganizationService, diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index a5bcb915713..82ddda66110 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -17,6 +17,7 @@ import { CipherViewLikeUtils, } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { SortDirection, TableDataSource } from "@bitwarden/components"; +import { OrganizationId } from "@bitwarden/sdk-internal"; import { GroupView } from "../../../admin-console/organizations/core"; @@ -579,7 +580,7 @@ export class VaultItemsComponent<C extends CipherViewLike> { .every(({ cipher }) => cipher?.edit && cipher?.viewPassword); } - private getUniqueOrganizationIds(): Set<string> { + private getUniqueOrganizationIds(): Set<string | [] | OrganizationId> { return new Set(this.selection.selected.flatMap((i) => i.cipher?.organizationId ?? [])); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts index 13f80920558..ec7397a22a8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts @@ -41,7 +41,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy { return convertToAccessPolicyItemViews(policies); }), ), - catchError(async () => { + catchError(async (): Promise<any> => { this.logService.info("Error fetching project people access policies."); await this.router.navigate(["/sm", this.organizationId, "projects"]); return undefined; diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts index f5ba2d0be23..25ae8a31ef6 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts @@ -75,7 +75,7 @@ describe("WebAuthnLoginStrategy", () => { // We must do this to make the mocked classes available for all the // assertCredential(...) tests. - global.PublicKeyCredential = MockPublicKeyCredential; + global.PublicKeyCredential = MockPublicKeyCredential as any; global.AuthenticatorAssertionResponse = MockAuthenticatorAssertionResponse; }); @@ -397,4 +397,8 @@ export class MockPublicKeyCredential implements PublicKeyCredential { static isUserVerifyingPlatformAuthenticatorAvailable(): Promise<boolean> { return Promise.resolve(false); } + + toJSON() { + throw new Error("Method not implemented."); + } } diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts index 56aa1139cda..b848cb2f902 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts @@ -38,7 +38,7 @@ describe("WebAuthnLoginService", () => { // We must do this to make the mocked classes available for all the // assertCredential(...) tests. - global.PublicKeyCredential = MockPublicKeyCredential; + global.PublicKeyCredential = MockPublicKeyCredential as any; global.AuthenticatorAssertionResponse = MockAuthenticatorAssertionResponse; // Save the original navigator @@ -316,6 +316,10 @@ class MockPublicKeyCredential implements PublicKeyCredential { static isUserVerifyingPlatformAuthenticatorAvailable(): Promise<boolean> { return Promise.resolve(false); } + + toJSON() { + throw new Error("Method not implemented."); + } } function buildCredentialAssertionOptions(): WebAuthnLoginCredentialAssertionOptionsView { diff --git a/libs/common/src/vault/utils/observable-utilities.ts b/libs/common/src/vault/utils/observable-utilities.ts index cdec51fc953..025da8a36f0 100644 --- a/libs/common/src/vault/utils/observable-utilities.ts +++ b/libs/common/src/vault/utils/observable-utilities.ts @@ -30,7 +30,7 @@ export function perUserCache$<TValue>( create(userId), clearBuffer$.pipe( filter((clearId) => clearId === userId || clearId === null), - map(() => null), + map((): any => null), ), ).pipe(shareReplay({ bufferSize: 1, refCount: false })); cache.set(userId, observable); diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts index 16588f92807..6d0266dd0f7 100644 --- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts @@ -28,7 +28,7 @@ describe("AdditionalOptionsSectionComponent", () => { let passwordRepromptService: MockProxy<PasswordRepromptService>; let passwordRepromptEnabled$: BehaviorSubject<boolean>; - const getInitialCipherView = jest.fn(() => null); + const getInitialCipherView = jest.fn((): any => null); const formStatusChange$ = new BehaviorSubject<"enabled" | "disabled">("enabled"); beforeEach(async () => { diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts index 3654dc4fe18..afbf1d86649 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts @@ -34,7 +34,7 @@ describe("AutofillOptionsComponent", () => { let domainSettingsService: MockProxy<DomainSettingsService>; let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>; let platformUtilsService: MockProxy<PlatformUtilsService>; - const getInitialCipherView = jest.fn(() => null); + const getInitialCipherView = jest.fn((): any => null); const formStatusChange$ = new BehaviorSubject<"enabled" | "disabled">("enabled"); beforeEach(async () => { diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts index 32baad189cf..9233f1fa405 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts @@ -20,7 +20,7 @@ describe("CardDetailsSectionComponent", () => { let registerChildFormSpy: jest.SpyInstance; let patchCipherSpy: jest.SpyInstance; - const getInitialCipherView = jest.fn(() => null); + const getInitialCipherView = jest.fn((): any => null); beforeEach(async () => { cipherFormProvider = mock<CipherFormContainer>({ getInitialCipherView }); diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts index 970e6b1fb9a..ac384ee3fd8 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts @@ -37,7 +37,7 @@ describe("CipherFormComponent", () => { provide: CipherFormCacheService, useValue: { init: jest.fn(), getCachedCipherView: jest.fn() }, }, - { provide: ViewCacheService, useValue: { signal: jest.fn(() => () => null) } }, + { provide: ViewCacheService, useValue: { signal: jest.fn(() => (): any => null) } }, { provide: ConfigService, useValue: mock<ConfigService>() }, ], }).compileComponents(); diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts index b07a50fd383..d6fe8a64921 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts @@ -42,7 +42,7 @@ describe("LoginDetailsSectionComponent", () => { let configService: MockProxy<ConfigService>; const collect = jest.fn().mockResolvedValue(null); - const getInitialCipherView = jest.fn(() => null); + const getInitialCipherView = jest.fn((): any => null); beforeEach(async () => { getInitialCipherView.mockClear(); diff --git a/package-lock.json b/package-lock.json index afcedcfaf74..d5c0472d387 100644 --- a/package-lock.json +++ b/package-lock.json @@ -173,7 +173,7 @@ "ts-loader": "9.5.2", "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", - "typescript": "5.5.4", + "typescript": "5.8.3", "typescript-eslint": "8.31.0", "typescript-strict-plugin": "2.4.4", "url": "0.11.4", @@ -38542,9 +38542,10 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 5015f8278b2..97c6cc0eeae 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,7 @@ "ts-loader": "9.5.2", "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", - "typescript": "5.5.4", + "typescript": "5.8.3", "typescript-eslint": "8.31.0", "typescript-strict-plugin": "2.4.4", "url": "0.11.4", From 6d98360b04d036104f299804ae1d0cd727ff7696 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:42:21 -0500 Subject: [PATCH 69/83] remove unused view component (#16582) --- .../organizations/collections/vault.module.ts | 2 - .../vault/individual-vault/vault.module.ts | 2 - .../individual-vault/view.component.html | 31 --- .../individual-vault/view.component.spec.ts | 148 ------------ .../vault/individual-vault/view.component.ts | 218 ------------------ 5 files changed, 401 deletions(-) delete mode 100644 apps/web/src/app/vault/individual-vault/view.component.html delete mode 100644 apps/web/src/app/vault/individual-vault/view.component.spec.ts delete mode 100644 apps/web/src/app/vault/individual-vault/view.component.ts diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.module.ts b/apps/web/src/app/admin-console/organizations/collections/vault.module.ts index 1a093ff8352..d7c6a468eba 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.module.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.module.ts @@ -2,7 +2,6 @@ import { NgModule } from "@angular/core"; import { SharedModule } from "../../../shared/shared.module"; import { OrganizationBadgeModule } from "../../../vault/individual-vault/organization-badge/organization-badge.module"; -import { ViewComponent } from "../../../vault/individual-vault/view.component"; import { CollectionDialogComponent } from "../shared/components/collection-dialog"; import { CollectionNameBadgeComponent } from "./collection-badge"; @@ -19,7 +18,6 @@ import { VaultComponent } from "./vault.component"; OrganizationBadgeModule, CollectionDialogComponent, VaultComponent, - ViewComponent, ], }) export class VaultModule {} diff --git a/apps/web/src/app/vault/individual-vault/vault.module.ts b/apps/web/src/app/vault/individual-vault/vault.module.ts index 573eceef64a..156e73b439a 100644 --- a/apps/web/src/app/vault/individual-vault/vault.module.ts +++ b/apps/web/src/app/vault/individual-vault/vault.module.ts @@ -10,7 +10,6 @@ import { OrganizationBadgeModule } from "./organization-badge/organization-badge import { PipesModule } from "./pipes/pipes.module"; import { VaultRoutingModule } from "./vault-routing.module"; import { VaultComponent } from "./vault.component"; -import { ViewComponent } from "./view.component"; @NgModule({ imports: [ @@ -23,7 +22,6 @@ import { ViewComponent } from "./view.component"; BulkDialogsModule, CollectionDialogComponent, VaultComponent, - ViewComponent, ], }) export class VaultModule {} diff --git a/apps/web/src/app/vault/individual-vault/view.component.html b/apps/web/src/app/vault/individual-vault/view.component.html deleted file mode 100644 index ac6db212362..00000000000 --- a/apps/web/src/app/vault/individual-vault/view.component.html +++ /dev/null @@ -1,31 +0,0 @@ -<bit-dialog dialogSize="large" background="alt"> - <span bitDialogTitle> - {{ cipherTypeString }} - </span> - <ng-container bitDialogContent> - <app-cipher-view [cipher]="cipher" [collections]="collections"></app-cipher-view> - </ng-container> - <ng-container bitDialogFooter> - <button - bitButton - (click)="edit()" - buttonType="primary" - type="button" - [disabled]="params.disableEdit" - > - {{ "edit" | i18n }} - </button> - <div class="tw-ml-auto"> - <button - bitButton - type="button" - buttonType="danger" - [appA11yTitle]="'delete' | i18n" - [bitAction]="delete" - [disabled]="!(canDeleteCipher$ | async)" - > - <i class="bwi bwi-trash bwi-lg bwi-fw" aria-hidden="true"></i> - </button> - </div> - </ng-container> -</bit-dialog> diff --git a/apps/web/src/app/vault/individual-vault/view.component.spec.ts b/apps/web/src/app/vault/individual-vault/view.component.spec.ts deleted file mode 100644 index d60a6313c67..00000000000 --- a/apps/web/src/app/vault/individual-vault/view.component.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { mock } from "jest-mock-extended"; -import { of } from "rxjs"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; -import { UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { TaskService } from "@bitwarden/common/vault/tasks"; -import { DIALOG_DATA, DialogRef, DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -import { ChangeLoginPasswordService } from "@bitwarden/vault"; - -import { ViewCipherDialogParams, ViewCipherDialogResult, ViewComponent } from "./view.component"; - -describe("ViewComponent", () => { - let component: ViewComponent; - let fixture: ComponentFixture<ViewComponent>; - - const mockCipher: CipherView = { - id: "cipher-id", - type: 1, - organizationId: "org-id", - isDeleted: false, - } as CipherView; - - const mockOrganization: Organization = { - id: "org-id", - name: "Test Organization", - } as Organization; - - const mockParams: ViewCipherDialogParams = { - cipher: mockCipher, - }; - const userId = Utils.newGuid() as UserId; - const accountService: FakeAccountService = mockAccountServiceWith(userId); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ViewComponent], - providers: [ - { provide: DIALOG_DATA, useValue: mockParams }, - { provide: DialogRef, useValue: mock<DialogRef>() }, - { provide: I18nService, useValue: { t: jest.fn().mockReturnValue("login") } }, - { provide: DialogService, useValue: mock<DialogService>() }, - { provide: CipherService, useValue: mock<CipherService>() }, - { provide: ToastService, useValue: mock<ToastService>() }, - { provide: MessagingService, useValue: mock<MessagingService>() }, - { - provide: AccountService, - useValue: accountService, - }, - { provide: LogService, useValue: mock<LogService>() }, - { - provide: OrganizationService, - useValue: { organizations$: jest.fn().mockReturnValue(of([mockOrganization])) }, - }, - { provide: CollectionService, useValue: mock<CollectionService>() }, - { provide: FolderService, useValue: mock<FolderService>() }, - { provide: KeyService, useValue: mock<KeyService>() }, - { - provide: BillingAccountProfileStateService, - useValue: mock<BillingAccountProfileStateService>(), - }, - { provide: ConfigService, useValue: mock<ConfigService>() }, - { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, - { - provide: CipherAuthorizationService, - useValue: { - canDeleteCipher$: jest.fn().mockReturnValue(true), - }, - }, - { provide: TaskService, useValue: mock<TaskService>() }, - ], - }) - .overrideComponent(ViewComponent, { - remove: { - providers: [ - { provide: PlatformUtilsService, useValue: PlatformUtilsService }, - { - provide: ChangeLoginPasswordService, - useValue: ChangeLoginPasswordService, - }, - ], - }, - add: { - providers: [ - { provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() }, - { - provide: ChangeLoginPasswordService, - useValue: mock<ChangeLoginPasswordService>(), - }, - ], - }, - }) - .compileComponents(); - - fixture = TestBed.createComponent(ViewComponent); - component = fixture.componentInstance; - component.params = mockParams; - component.cipher = mockCipher; - }); - - describe("ngOnInit", () => { - it("initializes the component with cipher and organization", async () => { - await component.ngOnInit(); - - expect(component.cipher).toEqual(mockCipher); - expect(component.organization).toEqual(mockOrganization); - }); - }); - - describe("edit", () => { - it("closes the dialog with the proper arguments", async () => { - const dialogRefCloseSpy = jest.spyOn(component["dialogRef"], "close"); - - await component.edit(); - - expect(dialogRefCloseSpy).toHaveBeenCalledWith({ action: ViewCipherDialogResult.Edited }); - }); - }); - - describe("delete", () => { - it("calls the delete method on delete and closes the dialog with the proper arguments", async () => { - const deleteSpy = jest.spyOn(component, "delete"); - const dialogRefCloseSpy = jest.spyOn(component["dialogRef"], "close"); - jest.spyOn(component["dialogService"], "openSimpleDialog").mockResolvedValue(true); - - await component.delete(); - - expect(deleteSpy).toHaveBeenCalled(); - expect(dialogRefCloseSpy).toHaveBeenCalledWith({ action: ViewCipherDialogResult.Deleted }); - }); - }); -}); diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts deleted file mode 100644 index 6de29f8e328..00000000000 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ /dev/null @@ -1,218 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, Inject, OnInit } from "@angular/core"; -import { firstValueFrom, map, Observable } from "rxjs"; - -import { CollectionView } from "@bitwarden/admin-console/common"; -import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { CollectionId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; -import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; -import { - DIALOG_DATA, - DialogRef, - DialogConfig, - AsyncActionsModule, - DialogModule, - DialogService, - ToastService, -} from "@bitwarden/components"; -import { CipherViewComponent } from "@bitwarden/vault"; - -import { SharedModule } from "../../shared/shared.module"; - -export interface ViewCipherDialogParams { - cipher: CipherView; - - /** - * Optional list of collections the cipher is assigned to. If none are provided, they will be loaded using the - * `CipherService` and the `collectionIds` property of the cipher. - */ - collections?: CollectionView[]; - - /** - * Optional collection ID used to know the collection filter selected. - */ - activeCollectionId?: CollectionId; - - /** - * If true, the edit button will be disabled in the dialog. - */ - disableEdit?: boolean; -} - -export const ViewCipherDialogResult = { - Edited: "edited", - Deleted: "deleted", - PremiumUpgrade: "premiumUpgrade", -} as const; - -type ViewCipherDialogResult = UnionOfValues<typeof ViewCipherDialogResult>; - -export interface ViewCipherDialogCloseResult { - action: ViewCipherDialogResult; -} - -/** - * Component for viewing a cipher, presented in a dialog. - * @deprecated Use the VaultItemDialogComponent instead. - */ -@Component({ - selector: "app-vault-view", - templateUrl: "view.component.html", - imports: [CipherViewComponent, CommonModule, AsyncActionsModule, DialogModule, SharedModule], - providers: [{ provide: ViewPasswordHistoryService, useClass: VaultViewPasswordHistoryService }], -}) -export class ViewComponent implements OnInit { - cipher: CipherView; - collections?: CollectionView[]; - onDeletedCipher = new EventEmitter<CipherView>(); - cipherTypeString: string; - organization: Organization; - - canDeleteCipher$: Observable<boolean>; - - constructor( - @Inject(DIALOG_DATA) public params: ViewCipherDialogParams, - private dialogRef: DialogRef<ViewCipherDialogCloseResult>, - private i18nService: I18nService, - private dialogService: DialogService, - private messagingService: MessagingService, - private logService: LogService, - private cipherService: CipherService, - private toastService: ToastService, - private organizationService: OrganizationService, - private cipherAuthorizationService: CipherAuthorizationService, - private accountService: AccountService, - ) {} - - /** - * Lifecycle hook for component initialization. - */ - async ngOnInit() { - this.cipher = this.params.cipher; - this.collections = this.params.collections; - this.cipherTypeString = this.getCipherViewTypeString(); - - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - - if (this.cipher.organizationId) { - this.organization = await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe( - map((organizations) => organizations.find((o) => o.id === this.cipher.organizationId)), - ), - ); - } - - this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher); - } - - /** - * Method to handle cipher deletion. Called when a user clicks the delete button. - */ - delete = async () => { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "deleteItem" }, - content: { - key: this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation", - }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - await this.deleteCipher(); - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("success"), - message: this.i18nService.t( - this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", - ), - }); - this.onDeletedCipher.emit(this.cipher); - this.messagingService.send( - this.cipher.isDeleted ? "permanentlyDeletedCipher" : "deletedCipher", - ); - } catch (e) { - this.logService.error(e); - } - - this.dialogRef.close({ action: ViewCipherDialogResult.Deleted }); - }; - - /** - * Helper method to delete cipher. - */ - protected async deleteCipher(): Promise<void> { - const asAdmin = this.organization?.canEditAllCiphers; - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - if (this.cipher.isDeleted) { - await this.cipherService.deleteWithServer(this.cipher.id, userId, asAdmin); - } else { - await this.cipherService.softDeleteWithServer(this.cipher.id, userId, asAdmin); - } - } - - /** - * Method to handle cipher editing. Called when a user clicks the edit button. - */ - async edit(): Promise<void> { - this.dialogRef.close({ action: ViewCipherDialogResult.Edited }); - } - - /** - * Method to get cipher view type string, used for the dialog title. - * E.g. "View login" or "View note". - * @returns The localized string for the cipher type - */ - getCipherViewTypeString(): string { - if (!this.cipher) { - return null; - } - - switch (this.cipher.type) { - case CipherType.Login: - return this.i18nService.t("viewItemHeaderLogin"); - case CipherType.SecureNote: - return this.i18nService.t("viewItemHeaderCard"); - case CipherType.Card: - return this.i18nService.t("viewItemHeaderIdentity"); - case CipherType.Identity: - return this.i18nService.t("viewItemHeaderNote"); - case CipherType.SshKey: - return this.i18nService.t("viewItemHeaderSshKey"); - default: - return null; - } - } -} - -/** - * Strongly typed helper to open a cipher view dialog - * @param dialogService Instance of the dialog service that will be used to open the dialog - * @param config Configuration for the dialog - * @returns A reference to the opened dialog - */ -export function openViewCipherDialog( - dialogService: DialogService, - config: DialogConfig<ViewCipherDialogParams>, -): DialogRef<ViewCipherDialogCloseResult> { - return dialogService.open(ViewComponent, config); -} From 8c81ccc1c58aa8eece79ba65c0e790b746ae9189 Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:01:06 -0500 Subject: [PATCH 70/83] [PM-25611][PM-25612] Update components to use persistance code (#16655) * Add password trigger logic to report service. Also updated api to use classes that properly handle encstring with placeholders for upcoming usage * Fix merged test case conflict * Fix type errors and test cases. Make create data functions for report and summary * Update Risk Insights Report Data Type * Update encryption usage and test cases. Moved mock data * Remove unused variable * Move all-application constructor * Update all applications and risk insights to look at fetched logic * Fix name of variable. Fetch last report run * Cleanup all and critical application tabs drawer dependencies * Rename components from tool to dirt. Hook up all applications to use reportResult summary * Critical application cleanup. Trigger refetch of report for enriching when critical applications change * Fix type errors * Rename loader from tools to dirt. Cleanup * Add activity tab updates using data service * Use safeProviders in access intelligence * Fix refresh button not appearing. Change "refresh" to "run report" * Remove multiple async calls for isRunningReport * Fix report button not showing * Add no report ran message * Fix password change on critical applications --- apps/web/src/locales/en/messages.json | 9 + .../helpers/risk-insights-data-mappers.ts | 10 +- .../risk-insights/models/api-models.types.ts | 17 +- .../reports/risk-insights/models/index.ts | 2 + .../reports/risk-insights/models/mock-data.ts | 140 +++++++++++ .../risk-insights/models/password-health.ts | 12 - .../models/report-data-service.types.ts | 18 ++ .../models/report-encryption.types.ts | 32 +++ .../risk-insights/models/report-models.ts | 22 +- .../services/all-activities.service.ts | 30 ++- .../services/critical-apps.service.spec.ts | 12 +- .../services/critical-apps.service.ts | 5 - .../risk-insights-api.service.spec.ts | 61 +++-- .../services/risk-insights-api.service.ts | 4 +- .../services/risk-insights-data.service.ts | 237 ++++++++++++------ .../risk-insights-encryption.service.spec.ts | 161 ++++++------ .../risk-insights-encryption.service.ts | 148 +++++++---- .../risk-insights-report.service.spec.ts | 168 ++++--------- .../services/risk-insights-report.service.ts | 132 +++++++--- .../access-intelligence.module.ts | 24 +- .../all-activity.component.html | 10 +- .../all-activity.component.ts | 3 +- .../all-applications.component.html | 190 +++++++------- .../all-applications.component.ts | 163 ++++-------- .../app-table-row-scrollable.component.ts | 4 +- .../critical-applications.component.html | 6 +- .../critical-applications.component.ts | 181 +++++-------- .../risk-insights-loading.component.ts | 2 +- .../risk-insights.component.html | 35 ++- .../risk-insights.component.ts | 63 ++--- 30 files changed, 1055 insertions(+), 846 deletions(-) create mode 100644 bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mock-data.ts create mode 100644 bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-data-service.types.ts create mode 100644 bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-encryption.types.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 7f08d3f02d1..dafd7f00a04 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -35,6 +35,9 @@ } } }, + "noReportRan": { + "message": "You have not created a report yet" + }, "notifiedMembers": { "message": "Notified members" }, @@ -187,6 +190,9 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "applicationsMarkedAsCriticalFail": { + "message": "Failed to mark applications as critical" + }, "application": { "message": "Application" }, @@ -4317,6 +4323,9 @@ "generatingYourRiskInsights": { "message": "Generating your Risk Insights..." }, + "riskInsightsRunReport": { + "message": "Run report" + }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." }, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts index 3f679924df9..487ac28e963 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts @@ -9,7 +9,7 @@ import { import { ApplicationHealthReportDetail, OrganizationReportSummary, - RiskInsightsReportData, + RiskInsightsData, } from "../models/report-models"; import { MemberCipherDetailsResponse } from "../response/member-cipher-details.response"; @@ -154,10 +154,12 @@ export function getApplicationReportDetail( * * @returns An empty report */ -export function createNewReportData(): RiskInsightsReportData { +export function createNewReportData(): RiskInsightsData { return { - data: [], - summary: createNewSummaryData(), + creationDate: new Date(), + reportData: [], + summaryData: createNewSummaryData(), + applicationData: [], }; } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/api-models.types.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/api-models.types.ts index 89293651a23..871db2b68ac 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/api-models.types.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/api-models.types.ts @@ -37,8 +37,10 @@ export interface PasswordHealthReportApplicationsRequest { export interface SaveRiskInsightsReportRequest { data: { organizationId: OrganizationId; - date: string; + creationDate: string; reportData: string; + summaryData: string; + applicationData: string; contentEncryptionKey: string; }; } @@ -58,9 +60,10 @@ export function isSaveRiskInsightsReportResponse(obj: any): obj is SaveRiskInsig export class GetRiskInsightsReportResponse extends BaseResponse { id: string; organizationId: OrganizationId; - // TODO Update to use creationDate from server - date: string; + creationDate: Date; reportData: EncString; + summaryData: EncString; + applicationData: EncString; contentEncryptionKey: EncString; constructor(response: any) { @@ -68,8 +71,10 @@ export class GetRiskInsightsReportResponse extends BaseResponse { this.id = this.getResponseProperty("organizationId"); this.organizationId = this.getResponseProperty("organizationId"); - this.date = this.getResponseProperty("date"); + this.creationDate = new Date(this.getResponseProperty("creationDate")); this.reportData = new EncString(this.getResponseProperty("reportData")); + this.summaryData = new EncString(this.getResponseProperty("summaryData")); + this.applicationData = new EncString(this.getResponseProperty("applicationData")); this.contentEncryptionKey = new EncString(this.getResponseProperty("contentEncryptionKey")); } } @@ -77,7 +82,7 @@ export class GetRiskInsightsReportResponse extends BaseResponse { export class GetRiskInsightsSummaryResponse extends BaseResponse { id: string; organizationId: OrganizationId; - encryptedData: EncString; // Decrypted as OrganizationReportSummary + encryptedSummary: EncString; // Decrypted as OrganizationReportSummary contentEncryptionKey: EncString; constructor(response: any) { @@ -85,7 +90,7 @@ export class GetRiskInsightsSummaryResponse extends BaseResponse { // TODO Handle taking array of summary data and converting to array this.id = this.getResponseProperty("id"); this.organizationId = this.getResponseProperty("organizationId"); - this.encryptedData = this.getResponseProperty("encryptedData"); + this.encryptedSummary = this.getResponseProperty("encryptedData"); this.contentEncryptionKey = this.getResponseProperty("contentEncryptionKey"); } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/index.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/index.ts index b8fcfe251ff..abe1f7200dc 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/index.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/index.ts @@ -1,3 +1,5 @@ export * from "./api-models.types"; export * from "./password-health"; +export * from "./report-data-service.types"; +export * from "./report-encryption.types"; export * from "./report-models"; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mock-data.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mock-data.ts new file mode 100644 index 00000000000..c790fc327a9 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mock-data.ts @@ -0,0 +1,140 @@ +import { mock } from "jest-mock-extended"; + +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { MemberCipherDetailsResponse } from "../response/member-cipher-details.response"; + +import { ApplicationHealthReportDetailEnriched } from "./report-data-service.types"; +import { + ApplicationHealthReportDetail, + OrganizationReportApplication, + OrganizationReportSummary, +} from "./report-models"; + +const mockApplication1: ApplicationHealthReportDetail = { + applicationName: "application1.com", + passwordCount: 2, + atRiskPasswordCount: 1, + atRiskCipherIds: ["cipher-1"], + memberCount: 2, + atRiskMemberCount: 1, + memberDetails: [ + { + userGuid: "user-id-1", + userName: "tom", + email: "tom@application1.com", + cipherId: "cipher-1", + }, + ], + atRiskMemberDetails: [ + { + userGuid: "user-id-2", + userName: "tom", + email: "tom2@application1.com", + cipherId: "cipher-2", + }, + ], + cipherIds: ["cipher-1", "cipher-2"], +}; + +const mockApplication2: ApplicationHealthReportDetail = { + applicationName: "site2.application1.com", + passwordCount: 0, + atRiskPasswordCount: 0, + atRiskCipherIds: [], + memberCount: 0, + atRiskMemberCount: 0, + memberDetails: [], + atRiskMemberDetails: [], + cipherIds: [], +}; +const mockApplication3: ApplicationHealthReportDetail = { + applicationName: "application2.com", + passwordCount: 0, + atRiskPasswordCount: 0, + atRiskCipherIds: [], + memberCount: 0, + atRiskMemberCount: 0, + memberDetails: [], + atRiskMemberDetails: [], + cipherIds: [], +}; + +export const mockReportData: ApplicationHealthReportDetail[] = [ + mockApplication1, + mockApplication2, + mockApplication3, +]; + +export const mockSummaryData: OrganizationReportSummary = { + totalMemberCount: 5, + totalAtRiskMemberCount: 2, + totalApplicationCount: 3, + totalAtRiskApplicationCount: 1, + totalCriticalMemberCount: 1, + totalCriticalAtRiskMemberCount: 1, + totalCriticalApplicationCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: [], +}; +export const mockApplicationData: OrganizationReportApplication[] = [ + { + applicationName: "application1.com", + isCritical: true, + }, + { + applicationName: "application2.com", + isCritical: false, + }, +]; + +export const mockEnrichedReportData: ApplicationHealthReportDetailEnriched[] = [ + { ...mockApplication1, isMarkedAsCritical: true, ciphers: [] }, + { ...mockApplication2, isMarkedAsCritical: false, ciphers: [] }, +]; + +export const mockCipherViews: CipherView[] = [ + mock<CipherView>({ + id: "cipher-1", + type: CipherType.Login, + login: { password: "pass1", username: "user1", uris: [{ uri: "https://app.com/login" }] }, + isDeleted: false, + viewPassword: true, + }), + mock<CipherView>({ + id: "cipher-2", + type: CipherType.Login, + login: { password: "pass2", username: "user2", uris: [{ uri: "app.com/home" }] }, + isDeleted: false, + viewPassword: true, + }), + mock<CipherView>({ + id: "cipher-3", + type: CipherType.Login, + login: { password: "pass3", username: "user3", uris: [{ uri: "https://other.com" }] }, + isDeleted: false, + viewPassword: true, + }), +]; + +export const mockMemberDetails = [ + mock<MemberCipherDetailsResponse>({ + cipherIds: ["cipher-1"], + userGuid: "user1", + userName: "User 1", + email: "user1@app.com", + }), + mock<MemberCipherDetailsResponse>({ + cipherIds: ["cipher-2"], + userGuid: "user2", + userName: "User 2", + email: "user2@app.com", + }), + mock<MemberCipherDetailsResponse>({ + cipherIds: ["cipher-3"], + userGuid: "user3", + userName: "User 3", + email: "user3@other.com", + }), +]; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index e026a4475b7..8127ea41085 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -1,7 +1,5 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeVariant } from "@bitwarden/components"; @@ -33,16 +31,6 @@ export type ExposedPasswordDetail = { exposedXTimes: number; } | null; -/* - * After data is encrypted, it is returned with the - * encryption key used to encrypt the data. - */ -export interface EncryptedDataWithKey { - organizationId: OrganizationId; - encryptedData: EncString; - contentEncryptionKey: EncString; -} - export type LEGACY_MemberDetailsFlat = { userGuid: string; userName: string; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-data-service.types.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-data-service.types.ts new file mode 100644 index 00000000000..6196c788ecd --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-data-service.types.ts @@ -0,0 +1,18 @@ +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { + ApplicationHealthReportDetail, + OrganizationReportApplication, + OrganizationReportSummary, +} from "./report-models"; + +export type ApplicationHealthReportDetailEnriched = ApplicationHealthReportDetail & { + isMarkedAsCritical: boolean; + ciphers: CipherView[]; +}; +export interface RiskInsightsEnrichedData { + reportData: ApplicationHealthReportDetailEnriched[]; + summaryData: OrganizationReportSummary; + applicationData: OrganizationReportApplication[]; + creationDate: Date; +} diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-encryption.types.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-encryption.types.ts new file mode 100644 index 00000000000..d5f2726d7ca --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-encryption.types.ts @@ -0,0 +1,32 @@ +import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { + ApplicationHealthReportDetail, + OrganizationReportApplication, + OrganizationReportSummary, +} from "./report-models"; + +/* + * After data is encrypted, it is returned with the + * encryption key used to encrypt the data. + */ +export interface EncryptedDataWithKey { + organizationId: OrganizationId; + encryptedReportData: EncString; + encryptedSummaryData: EncString; + encryptedApplicationData: EncString; + contentEncryptionKey: EncString; +} + +export interface DecryptedReportData { + reportData: ApplicationHealthReportDetail[]; + summaryData: OrganizationReportSummary; + applicationData: OrganizationReportApplication[]; +} + +export interface EncryptedReportData { + encryptedReportData: EncString; + encryptedSummaryData: EncString; + encryptedApplicationData: EncString; +} diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts index 1758bb41b1b..564f483813a 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts @@ -131,11 +131,6 @@ export type ApplicationHealthReportDetail = { cipherIds: string[]; }; -export type ApplicationHealthReportDetailEnriched = ApplicationHealthReportDetail & { - isMarkedAsCritical: boolean; - ciphers: CipherView[]; -}; - /* * A list of applications and the count of * at risk passwords for each application @@ -148,12 +143,6 @@ export type AtRiskApplicationDetail = { // -------------------- Password Health Report Models -------------------- export type PasswordHealthReportApplicationId = Opaque<string, "PasswordHealthReportApplicationId">; -// -------------------- Risk Insights Report Models -------------------- -export interface RiskInsightsReportData { - data: ApplicationHealthReportDetailEnriched[]; - summary: OrganizationReportSummary; -} - export type ReportScore = { label: string; badgeVariant: BadgeVariant; sortOrder: number }; export type ReportResult = CipherView & { @@ -162,8 +151,9 @@ export type ReportResult = CipherView & { scoreKey: number; }; -export type ReportDetailsAndSummary = { - data: ApplicationHealthReportDetailEnriched[]; - summary: OrganizationReportSummary; - dateCreated: Date; -}; +export interface RiskInsightsData { + creationDate: Date; + reportData: ApplicationHealthReportDetail[]; + summaryData: OrganizationReportSummary; + applicationData: OrganizationReportApplication[]; +} diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts index 3ea67d8f7c9..b8992b1a05f 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts @@ -1,8 +1,10 @@ import { BehaviorSubject } from "rxjs"; -import { LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher } from "../models"; +import { ApplicationHealthReportDetailEnriched } from "../models"; import { OrganizationReportSummary } from "../models/report-models"; +import { RiskInsightsDataService } from "./risk-insights-data.service"; + export class AllActivitiesService { /// This class is used to manage the summary of all applications /// and critical applications. @@ -20,12 +22,10 @@ export class AllActivitiesService { totalCriticalAtRiskApplicationCount: 0, newApplications: [], }); - reportSummary$ = this.reportSummarySubject$.asObservable(); - private allApplicationsDetailsSubject$: BehaviorSubject< - LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[] - > = new BehaviorSubject<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[]>([]); + private allApplicationsDetailsSubject$: BehaviorSubject<ApplicationHealthReportDetailEnriched[]> = + new BehaviorSubject<ApplicationHealthReportDetailEnriched[]>([]); allApplicationsDetails$ = this.allApplicationsDetailsSubject$.asObservable(); private atRiskPasswordsCountSubject$ = new BehaviorSubject<number>(0); @@ -35,6 +35,22 @@ export class AllActivitiesService { passwordChangeProgressMetricHasProgressBar$ = this.passwordChangeProgressMetricHasProgressBarSubject$.asObservable(); + constructor(private dataService: RiskInsightsDataService) { + // All application summary changes + this.dataService.reportResults$.subscribe((report) => { + if (report) { + this.setAllAppsReportSummary(report.summaryData); + this.setAllAppsReportDetails(report.reportData); + } + }); + // Critical application summary changes + this.dataService.criticalReportResults$.subscribe((report) => { + if (report) { + this.setCriticalAppsReportSummary(report.summaryData); + } + }); + } + setCriticalAppsReportSummary(summary: OrganizationReportSummary) { this.reportSummarySubject$.next({ ...this.reportSummarySubject$.getValue(), @@ -55,9 +71,7 @@ export class AllActivitiesService { }); } - setAllAppsReportDetails( - applications: LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher[], - ) { + setAllAppsReportDetails(applications: ApplicationHealthReportDetailEnriched[]) { const totalAtRiskPasswords = applications.reduce( (sum, app) => sum + app.atRiskPasswordCount, 0, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.spec.ts index 72d7e88fcab..28d670f226d 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.spec.ts @@ -82,9 +82,10 @@ describe("CriticalAppsService", () => { }); it("should exclude records that already exist", async () => { + const privateCriticalAppsSubject = service["criticalAppsListSubject$"]; // arrange // one record already exists - service.setAppsInListForOrg([ + privateCriticalAppsSubject.next([ { id: randomUUID() as PasswordHealthReportApplicationId, organizationId: SomeOrganization, @@ -145,6 +146,7 @@ describe("CriticalAppsService", () => { it("should get by org id", () => { const orgId = "some organization" as OrganizationId; + const privateCriticalAppsSubject = service["criticalAppsListSubject$"]; const response = [ { id: "id1", organizationId: "some organization", uri: "https://example.com" }, { id: "id2", organizationId: "some organization", uri: "https://example.org" }, @@ -155,13 +157,14 @@ describe("CriticalAppsService", () => { const orgKey$ = new BehaviorSubject(OrgRecords); keyService.orgKeys$.mockReturnValue(orgKey$); service.loadOrganizationContext(SomeOrganization, SomeUser); - service.setAppsInListForOrg(response); + privateCriticalAppsSubject.next(response); service.getAppsListForOrg(orgId as OrganizationId).subscribe((res) => { expect(res).toHaveLength(2); }); }); it("should drop a critical app", async () => { + const privateCriticalAppsSubject = service["criticalAppsListSubject$"]; // arrange const selectedUrl = "https://example.com"; @@ -175,7 +178,7 @@ describe("CriticalAppsService", () => { service.loadOrganizationContext(SomeOrganization, SomeUser); - service.setAppsInListForOrg(initialList); + privateCriticalAppsSubject.next(initialList); // act await service.dropCriticalApp(SomeOrganization, selectedUrl); @@ -193,6 +196,7 @@ describe("CriticalAppsService", () => { }); it("should not drop a critical app if it does not exist", async () => { + const privateCriticalAppsSubject = service["criticalAppsListSubject$"]; // arrange const selectedUrl = "https://nonexistent.com"; @@ -206,7 +210,7 @@ describe("CriticalAppsService", () => { service.loadOrganizationContext(SomeOrganization, SomeUser); - service.setAppsInListForOrg(initialList); + privateCriticalAppsSubject.next(initialList); // act await service.dropCriticalApp(SomeOrganization, selectedUrl); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts index 82001387bbd..b3b2f7c44e8 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts @@ -83,11 +83,6 @@ export class CriticalAppsService { .pipe(map((apps) => apps.filter((app) => app.organizationId === orgId))); } - // Reset the critical apps list - setAppsInListForOrg(apps: PasswordHealthReportApplicationsResponse[]) { - this.criticalAppsListSubject$.next(apps); - } - // Save the selected critical apps for a given organization async setCriticalApps(orgId: OrganizationId, selectedUrls: string[]) { if (orgId != this.organizationId.value) { diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.spec.ts index 4eda92f0eb3..56246f3c3b6 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.spec.ts @@ -7,6 +7,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { makeEncString } from "@bitwarden/common/spec"; import { OrganizationId, OrganizationReportId } from "@bitwarden/common/types/guid"; +import { EncryptedDataWithKey } from "../models"; import { GetRiskInsightsApplicationDataResponse, GetRiskInsightsReportResponse, @@ -14,7 +15,7 @@ import { SaveRiskInsightsReportRequest, SaveRiskInsightsReportResponse, } from "../models/api-models.types"; -import { EncryptedDataWithKey } from "../models/password-health"; +import { mockApplicationData, mockReportData, mockSummaryData } from "../models/mock-data"; import { RiskInsightsApiService } from "./risk-insights-api.service"; @@ -26,17 +27,21 @@ describe("RiskInsightsApiService", () => { const orgId = "org1" as OrganizationId; const mockReportId = "report-1"; const mockKey = "encryption-key-1"; - const mockData = "encrypted-data"; - const reportData = makeEncString("test").encryptedString?.toString() ?? ""; - const reportKey = makeEncString("test-key").encryptedString?.toString() ?? ""; + const mockReportKey = makeEncString("test-key"); - const saveRiskInsightsReportRequest: SaveRiskInsightsReportRequest = { + const mockReportEnc = makeEncString(JSON.stringify(mockReportData)); + const mockSummaryEnc = makeEncString(JSON.stringify(mockSummaryData)); + const mockApplicationsEnc = makeEncString(JSON.stringify(mockApplicationData)); + + const mockSaveRiskInsightsReportRequest: SaveRiskInsightsReportRequest = { data: { organizationId: orgId, - date: new Date().toISOString(), - reportData: reportData, - contentEncryptionKey: reportKey, + creationDate: new Date().toISOString(), + reportData: mockReportEnc.decryptedValue ?? "", + summaryData: mockReportEnc.decryptedValue ?? "", + applicationData: mockReportEnc.decryptedValue ?? "", + contentEncryptionKey: mockReportKey.decryptedValue ?? "", }, }; @@ -53,7 +58,9 @@ describe("RiskInsightsApiService", () => { id: mockId, organizationId: orgId, date: new Date().toISOString(), - reportData: mockData, + reportData: mockReportEnc, + summaryData: mockSummaryEnc, + applicationData: mockApplicationsEnc, contentEncryptionKey: mockKey, }; @@ -96,17 +103,17 @@ describe("RiskInsightsApiService", () => { }); it("saveRiskInsightsReport$ should call apiService.send with correct parameters", async () => { - mockApiService.send.mockReturnValue(Promise.resolve(saveRiskInsightsReportRequest)); + mockApiService.send.mockReturnValue(Promise.resolve(mockSaveRiskInsightsReportRequest)); const result = await firstValueFrom( - service.saveRiskInsightsReport$(saveRiskInsightsReportRequest, orgId), + service.saveRiskInsightsReport$(mockSaveRiskInsightsReportRequest, orgId), ); - expect(result).toEqual(new SaveRiskInsightsReportResponse(saveRiskInsightsReportRequest)); + expect(result).toEqual(new SaveRiskInsightsReportResponse(mockSaveRiskInsightsReportRequest)); expect(mockApiService.send).toHaveBeenCalledWith( "POST", `/reports/organizations/${orgId.toString()}`, - saveRiskInsightsReportRequest.data, + mockSaveRiskInsightsReportRequest.data, true, true, ); @@ -117,13 +124,13 @@ describe("RiskInsightsApiService", () => { mockApiService.send.mockReturnValue(Promise.reject(error)); await expect( - firstValueFrom(service.saveRiskInsightsReport$(saveRiskInsightsReportRequest, orgId)), + firstValueFrom(service.saveRiskInsightsReport$(mockSaveRiskInsightsReportRequest, orgId)), ).rejects.toEqual(error); expect(mockApiService.send).toHaveBeenCalledWith( "POST", `/reports/organizations/${orgId.toString()}`, - saveRiskInsightsReportRequest.data, + mockSaveRiskInsightsReportRequest.data, true, true, ); @@ -134,13 +141,13 @@ describe("RiskInsightsApiService", () => { mockApiService.send.mockReturnValue(Promise.reject(error)); await expect( - firstValueFrom(service.saveRiskInsightsReport$(saveRiskInsightsReportRequest, orgId)), + firstValueFrom(service.saveRiskInsightsReport$(mockSaveRiskInsightsReportRequest, orgId)), ).rejects.toEqual(error); expect(mockApiService.send).toHaveBeenCalledWith( "POST", `/reports/organizations/${orgId.toString()}`, - saveRiskInsightsReportRequest.data, + mockSaveRiskInsightsReportRequest.data, true, true, ); @@ -153,7 +160,7 @@ describe("RiskInsightsApiService", () => { { reportId: mockReportId, organizationId: orgId, - encryptedData: mockData, + encryptedData: mockReportData, contentEncryptionKey: mockKey, }, ]; @@ -175,8 +182,10 @@ describe("RiskInsightsApiService", () => { it("updateRiskInsightsSummary$ should call apiService.send with correct parameters and return an Observable", async () => { const data: EncryptedDataWithKey = { organizationId: orgId, - encryptedData: new EncString(mockData), contentEncryptionKey: new EncString(mockKey), + encryptedReportData: new EncString(JSON.stringify(mockReportData)), + encryptedSummaryData: new EncString(JSON.stringify(mockSummaryData)), + encryptedApplicationData: new EncString(JSON.stringify(mockApplicationData)), }; const reportId = "report123" as OrganizationReportId; @@ -199,7 +208,9 @@ describe("RiskInsightsApiService", () => { const reportId = "report123" as OrganizationReportId; const mockResponse: EncryptedDataWithKey | null = { organizationId: orgId, - encryptedData: new EncString(mockData), + encryptedReportData: new EncString(JSON.stringify(mockReportData)), + encryptedSummaryData: new EncString(JSON.stringify(mockSummaryData)), + encryptedApplicationData: new EncString(JSON.stringify(mockApplicationData)), contentEncryptionKey: new EncString(mockKey), }; @@ -217,21 +228,17 @@ describe("RiskInsightsApiService", () => { }); it("updateRiskInsightsApplicationData$ should call apiService.send with correct parameters and return an Observable", async () => { - const applicationData: EncryptedDataWithKey = { - organizationId: orgId, - encryptedData: new EncString(mockData), - contentEncryptionKey: new EncString(mockKey), - }; const reportId = "report123" as OrganizationReportId; + const mockApplication = mockApplicationData[0]; mockApiService.send.mockResolvedValueOnce(undefined); const result = await firstValueFrom( - service.updateRiskInsightsApplicationData$(applicationData, orgId, reportId), + service.updateRiskInsightsApplicationData$(mockApplication, orgId, reportId), ); expect(mockApiService.send).toHaveBeenCalledWith( "PATCH", `/reports/organizations/${orgId.toString()}/data/application/${reportId.toString()}`, - applicationData, + mockApplication, true, true, ); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.ts index 8f40ae91b47..99bf27506be 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-api.service.ts @@ -4,6 +4,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { OrganizationId, OrganizationReportId } from "@bitwarden/common/types/guid"; +import { EncryptedDataWithKey, OrganizationReportApplication } from "../models"; import { GetRiskInsightsApplicationDataResponse, GetRiskInsightsReportResponse, @@ -11,7 +12,6 @@ import { SaveRiskInsightsReportRequest, SaveRiskInsightsReportResponse, } from "../models/api-models.types"; -import { EncryptedDataWithKey } from "../models/password-health"; export class RiskInsightsApiService { constructor(private apiService: ApiService) {} @@ -102,7 +102,7 @@ export class RiskInsightsApiService { } updateRiskInsightsApplicationData$( - applicationData: EncryptedDataWithKey, + applicationData: OrganizationReportApplication, orgId: OrganizationId, reportId: OrganizationReportId, ): Observable<void> { diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-data.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-data.service.ts index 7038844998d..6b775f8432e 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-data.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-data.service.ts @@ -1,10 +1,12 @@ -import { BehaviorSubject, EMPTY, firstValueFrom, Observable, of } from "rxjs"; +import { BehaviorSubject, EMPTY, firstValueFrom, Observable, of, throwError } from "rxjs"; import { + catchError, distinctUntilChanged, exhaustMap, filter, finalize, map, + shareReplay, switchMap, tap, withLatestFrom, @@ -18,19 +20,13 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; -import { - AppAtRiskMembersDialogParams, - AtRiskApplicationDetail, - AtRiskMemberDetail, - DrawerType, - DrawerDetails, - ApplicationHealthReportDetail, - ApplicationHealthReportDetailEnriched, - ReportDetailsAndSummary, -} from "../models/report-models"; +import { ApplicationHealthReportDetailEnriched } from "../models"; +import { RiskInsightsEnrichedData } from "../models/report-data-service.types"; +import { DrawerType, DrawerDetails, ApplicationHealthReportDetail } from "../models/report-models"; import { CriticalAppsService } from "./critical-apps.service"; import { RiskInsightsReportService } from "./risk-insights-report.service"; + export class RiskInsightsDataService { // -------------------------- Context state -------------------------- // Current user viewing risk insights @@ -45,16 +41,17 @@ export class RiskInsightsDataService { organizationDetails$ = this.organizationDetailsSubject.asObservable(); // -------------------------- Data ------------------------------------ - private applicationsSubject = new BehaviorSubject<ApplicationHealthReportDetail[] | null>(null); - applications$ = this.applicationsSubject.asObservable(); + // TODO: Remove. Will use report results + private LEGACY_applicationsSubject = new BehaviorSubject<ApplicationHealthReportDetail[] | null>( + null, + ); + LEGACY_applications$ = this.LEGACY_applicationsSubject.asObservable(); - private dataLastUpdatedSubject = new BehaviorSubject<Date | null>(null); - dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable(); - - criticalApps$ = this.criticalAppsService.criticalAppsList$; + // TODO: Remove. Will use date from report results + private LEGACY_dataLastUpdatedSubject = new BehaviorSubject<Date | null>(null); + dataLastUpdated$ = this.LEGACY_dataLastUpdatedSubject.asObservable(); // --------------------------- UI State ------------------------------------ - private isLoadingSubject = new BehaviorSubject<boolean>(false); isLoading$ = this.isLoadingSubject.asObservable(); @@ -78,21 +75,52 @@ export class RiskInsightsDataService { // ------------------------- Report Variables ---------------- // The last run report details - private reportResultsSubject = new BehaviorSubject<ReportDetailsAndSummary | null>(null); + private reportResultsSubject = new BehaviorSubject<RiskInsightsEnrichedData | null>(null); reportResults$ = this.reportResultsSubject.asObservable(); // Is a report being generated private isRunningReportSubject = new BehaviorSubject<boolean>(false); isRunningReport$ = this.isRunningReportSubject.asObservable(); - // The error from report generation if there was an error + + // --------------------------- Critical Application data --------------------- + criticalReportResults$: Observable<RiskInsightsEnrichedData | null> = of(null); constructor( private accountService: AccountService, private criticalAppsService: CriticalAppsService, private organizationService: OrganizationService, private reportService: RiskInsightsReportService, - ) {} + ) { + // Reload report if critical applications change + // This also handles the original report load + this.criticalAppsService.criticalAppsList$ + .pipe(withLatestFrom(this.organizationDetails$, this.userId$)) + .subscribe({ + next: ([_criticalApps, organizationDetails, userId]) => { + if (organizationDetails?.organizationId && userId) { + this.fetchLastReport(organizationDetails?.organizationId, userId); + } + }, + }); + + // Setup critical application data and summary generation for live critical application usage + this.criticalReportResults$ = this.reportResults$.pipe( + filter((report) => !!report), + map((r) => { + const criticalApplications = r.reportData.filter( + (application) => application.isMarkedAsCritical, + ); + const summary = this.reportService.generateApplicationsSummary(criticalApplications); + + return { + ...r, + summaryData: summary, + reportData: criticalApplications, + }; + }), + shareReplay({ bufferSize: 1, refCount: true }), + ); + } - // [FIXME] PM-25612 - Call Initialization in RiskInsightsComponent instead of child components async initializeForOrganization(organizationId: OrganizationId) { // Fetch current user const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); @@ -115,9 +143,6 @@ export class RiskInsightsDataService { // Load critical applications for organization await this.criticalAppsService.loadOrganizationContext(organizationId, userId); - // Load existing report - this.fetchLastReport(organizationId, userId); - // Setup new report generation this._runApplicationsReport().subscribe({ next: (result) => { @@ -133,7 +158,7 @@ export class RiskInsightsDataService { * Fetches the applications report and updates the applicationsSubject. * @param organizationId The ID of the organization. */ - fetchApplicationsReport(organizationId: OrganizationId, isRefresh?: boolean): void { + LEGACY_fetchApplicationsReport(organizationId: OrganizationId, isRefresh?: boolean): void { if (isRefresh) { this.isRefreshingSubject.next(true); } else { @@ -145,24 +170,20 @@ export class RiskInsightsDataService { finalize(() => { this.isLoadingSubject.next(false); this.isRefreshingSubject.next(false); - this.dataLastUpdatedSubject.next(new Date()); + this.LEGACY_dataLastUpdatedSubject.next(new Date()); }), ) .subscribe({ next: (reports: ApplicationHealthReportDetail[]) => { - this.applicationsSubject.next(reports); + this.LEGACY_applicationsSubject.next(reports); this.errorSubject.next(null); }, error: () => { - this.applicationsSubject.next([]); + this.LEGACY_applicationsSubject.next([]); }, }); } - refreshApplicationsReport(organizationId: OrganizationId): void { - this.fetchApplicationsReport(organizationId, true); - } - // ------------------------------- Enrichment methods ------------------------------- /** * Takes the basic application health report details and enriches them to include @@ -174,8 +195,10 @@ export class RiskInsightsDataService { enrichReportData$( applications: ApplicationHealthReportDetail[], ): Observable<ApplicationHealthReportDetailEnriched[]> { + // TODO Compare applications on report to updated critical applications + // TODO Compare applications on report to any new applications return of(applications).pipe( - withLatestFrom(this.organizationDetails$, this.criticalApps$), + withLatestFrom(this.organizationDetails$, this.criticalAppsService.criticalAppsList$), switchMap(async ([apps, orgDetails, criticalApps]) => { if (!orgDetails) { return []; @@ -200,19 +223,11 @@ export class RiskInsightsDataService { ); } - // ------------------------------- Drawer management methods ------------------------------- // ------------------------- Drawer functions ----------------------------- - - isActiveDrawerType$ = (drawerType: DrawerType): Observable<boolean> => { - return this.drawerDetails$.pipe(map((details) => details.activeDrawerType === drawerType)); - }; isActiveDrawerType = (drawerType: DrawerType): boolean => { return this.drawerDetailsSubject.value.activeDrawerType === drawerType; }; - isDrawerOpenForInvoker$ = (applicationName: string) => { - return this.drawerDetails$.pipe(map((details) => details.invokerId === applicationName)); - }; isDrawerOpenForInvoker = (applicationName: string): boolean => { return this.drawerDetailsSubject.value.invokerId === applicationName; }; @@ -228,10 +243,7 @@ export class RiskInsightsDataService { }); }; - setDrawerForOrgAtRiskMembers = ( - atRiskMemberDetails: AtRiskMemberDetail[], - invokerId: string = "", - ): void => { + setDrawerForOrgAtRiskMembers = async (invokerId: string = ""): Promise<void> => { const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value; const shouldClose = open && activeDrawerType === DrawerType.OrgAtRiskMembers && currentInvokerId === invokerId; @@ -239,6 +251,15 @@ export class RiskInsightsDataService { if (shouldClose) { this.closeDrawer(); } else { + const reportResults = await firstValueFrom(this.reportResults$); + if (!reportResults) { + return; + } + + const atRiskMemberDetails = this.reportService.generateAtRiskMemberList( + reportResults.reportData, + ); + this.drawerDetailsSubject.next({ open: true, invokerId, @@ -250,10 +271,7 @@ export class RiskInsightsDataService { } }; - setDrawerForAppAtRiskMembers = ( - atRiskMembersDialogParams: AppAtRiskMembersDialogParams, - invokerId: string = "", - ): void => { + setDrawerForAppAtRiskMembers = async (invokerId: string = ""): Promise<void> => { const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value; const shouldClose = open && activeDrawerType === DrawerType.AppAtRiskMembers && currentInvokerId === invokerId; @@ -261,21 +279,29 @@ export class RiskInsightsDataService { if (shouldClose) { this.closeDrawer(); } else { + const reportResults = await firstValueFrom(this.reportResults$); + if (!reportResults) { + return; + } + + const atRiskMembers = { + members: + reportResults.reportData.find((app) => app.applicationName === invokerId) + ?.atRiskMemberDetails ?? [], + applicationName: invokerId, + }; this.drawerDetailsSubject.next({ open: true, invokerId, activeDrawerType: DrawerType.AppAtRiskMembers, atRiskMemberDetails: [], - appAtRiskMembers: atRiskMembersDialogParams, + appAtRiskMembers: atRiskMembers, atRiskAppDetails: null, }); } }; - setDrawerForOrgAtRiskApps = ( - atRiskApps: AtRiskApplicationDetail[], - invokerId: string = "", - ): void => { + setDrawerForOrgAtRiskApps = async (invokerId: string = ""): Promise<void> => { const { open, activeDrawerType, invokerId: currentInvokerId } = this.drawerDetailsSubject.value; const shouldClose = open && activeDrawerType === DrawerType.OrgAtRiskApps && currentInvokerId === invokerId; @@ -283,13 +309,21 @@ export class RiskInsightsDataService { if (shouldClose) { this.closeDrawer(); } else { + const reportResults = await firstValueFrom(this.reportResults$); + if (!reportResults) { + return; + } + const atRiskAppDetails = this.reportService.generateAtRiskApplicationList( + reportResults.reportData, + ); + this.drawerDetailsSubject.next({ open: true, invokerId, activeDrawerType: DrawerType.OrgAtRiskApps, atRiskMemberDetails: [], appAtRiskMembers: null, - atRiskAppDetails: atRiskApps, + atRiskAppDetails, }); } }; @@ -311,23 +345,31 @@ export class RiskInsightsDataService { .getRiskInsightsReport$(organizationId, userId) .pipe( switchMap((report) => { - return this.enrichReportData$(report.data).pipe( + // Take fetched report data and merge with critical applications + return this.enrichReportData$(report.reportData).pipe( map((enrichedReport) => ({ - data: enrichedReport, - summary: report.summary, + report: enrichedReport, + summary: report.summaryData, + applications: report.applicationData, + creationDate: report.creationDate, })), ); }), + catchError((error: unknown) => { + // console.error("An error occurred when fetching the last report", error); + return EMPTY; + }), finalize(() => { this.isLoadingSubject.next(false); }), ) .subscribe({ - next: ({ data, summary }) => { + next: ({ report, summary, applications, creationDate }) => { this.reportResultsSubject.next({ - data, - summary, - dateCreated: new Date(), + reportData: report, + summaryData: summary, + applicationData: applications, + creationDate: creationDate, }); this.errorSubject.next(null); this.isLoadingSubject.next(false); @@ -343,6 +385,7 @@ export class RiskInsightsDataService { private _runApplicationsReport() { return this.isRunningReport$.pipe( distinctUntilChanged(), + // Only run this report if the flag for running is true filter((isRunning) => isRunning), withLatestFrom(this.organizationDetails$, this.userId$), exhaustMap(([_, organizationDetails, userId]) => { @@ -353,22 +396,30 @@ export class RiskInsightsDataService { // Generate the report return this.reportService.generateApplicationsReport$(organizationId).pipe( - map((data) => ({ - data, - summary: this.reportService.generateApplicationsSummary(data), + map((report) => ({ + report, + summary: this.reportService.generateApplicationsSummary(report), + applications: this.reportService.generateOrganizationApplications(report), })), - switchMap(({ data, summary }) => - this.enrichReportData$(data).pipe( - map((enrichedData) => ({ data: enrichedData, summary })), + // Enrich report with critical markings + switchMap(({ report, summary, applications }) => + this.enrichReportData$(report).pipe( + map((enrichedReport) => ({ report: enrichedReport, summary, applications })), ), ), - tap(({ data, summary }) => { - this.reportResultsSubject.next({ data, summary, dateCreated: new Date() }); + // Load the updated data into the UI + tap(({ report, summary, applications }) => { + this.reportResultsSubject.next({ + reportData: report, + summaryData: summary, + applicationData: applications, + creationDate: new Date(), + }); this.errorSubject.next(null); }), - switchMap(({ data, summary }) => { - // Just returns ID - return this.reportService.saveRiskInsightsReport$(data, summary, { + switchMap(({ report, summary, applications }) => { + // Save the generated data + return this.reportService.saveRiskInsightsReport$(report, summary, applications, { organizationId, userId, }); @@ -377,4 +428,42 @@ export class RiskInsightsDataService { }), ); } + + // ------------------------------ Critical application methods -------------- + + saveCriticalApplications(selectedUrls: string[]) { + return this.organizationDetails$.pipe( + exhaustMap((organizationDetails) => { + if (!organizationDetails?.organizationId) { + return EMPTY; + } + return this.criticalAppsService.setCriticalApps( + organizationDetails?.organizationId, + selectedUrls, + ); + }), + catchError((error: unknown) => { + this.errorSubject.next("Failed to save critical applications"); + return throwError(() => error); + }), + ); + } + + removeCriticalApplication(hostname: string) { + return this.organizationDetails$.pipe( + exhaustMap((organizationDetails) => { + if (!organizationDetails?.organizationId) { + return EMPTY; + } + return this.criticalAppsService.dropCriticalApp( + organizationDetails?.organizationId, + hostname, + ); + }), + catchError((error: unknown) => { + this.errorSubject.next("Failed to remove critical application"); + return throwError(() => error); + }), + ); + } } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.spec.ts index 9b7bb3b7258..e2c92ad4b9b 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.spec.ts @@ -10,6 +10,9 @@ import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { OrgKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; +import { EncryptedReportData, DecryptedReportData } from "../models"; +import { mockApplicationData, mockReportData, mockSummaryData } from "../models/mock-data"; + import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service"; describe("RiskInsightsEncryptionService", () => { @@ -31,6 +34,10 @@ describe("RiskInsightsEncryptionService", () => { }; const orgKey$ = new BehaviorSubject(OrgRecords); + let mockDecryptedData: DecryptedReportData; + let mockEncryptedData: EncryptedReportData; + let mockKey: EncString; + beforeEach(() => { service = new RiskInsightsEncryptionService( mockKeyService, @@ -47,6 +54,18 @@ describe("RiskInsightsEncryptionService", () => { mockEncryptService.unwrapSymmetricKey.mockResolvedValue(contentEncryptionKey); mockEncryptService.decryptString.mockResolvedValue(JSON.stringify(testData)); mockKeyService.orgKeys$.mockReturnValue(orgKey$); + + mockKey = new EncString("wrapped-key"); + mockEncryptedData = { + encryptedReportData: new EncString(JSON.stringify(mockReportData)), + encryptedSummaryData: new EncString(JSON.stringify(mockSummaryData)), + encryptedApplicationData: new EncString(JSON.stringify(mockApplicationData)), + }; + mockDecryptedData = { + reportData: mockReportData, + summaryData: mockSummaryData, + applicationData: mockApplicationData, + }; }); describe("encryptRiskInsightsReport", () => { @@ -55,22 +74,40 @@ describe("RiskInsightsEncryptionService", () => { mockKeyService.orgKeys$.mockReturnValue(orgKey$); // Act: call the method under test - const result = await service.encryptRiskInsightsReport(orgId, userId, testData); + const result = await service.encryptRiskInsightsReport( + { organizationId: orgId, userId }, + mockDecryptedData, + ); // Assert: ensure that the methods were called with the expected parameters expect(mockKeyService.orgKeys$).toHaveBeenCalledWith(userId); expect(mockKeyGenerationService.createKey).toHaveBeenCalledWith(512); + + // Assert all variables were encrypted expect(mockEncryptService.encryptString).toHaveBeenCalledWith( - JSON.stringify(testData), + JSON.stringify(mockDecryptedData.reportData), contentEncryptionKey, ); + expect(mockEncryptService.encryptString).toHaveBeenCalledWith( + JSON.stringify(mockDecryptedData.summaryData), + contentEncryptionKey, + ); + expect(mockEncryptService.encryptString).toHaveBeenCalledWith( + JSON.stringify(mockDecryptedData.applicationData), + contentEncryptionKey, + ); + expect(mockEncryptService.wrapSymmetricKey).toHaveBeenCalledWith( contentEncryptionKey, orgKey, ); + + // Mocked encrypt returns ENCRYPTED_TEXT expect(result).toEqual({ organizationId: orgId, - encryptedData: new EncString(ENCRYPTED_TEXT), + encryptedReportData: new EncString(ENCRYPTED_TEXT), + encryptedSummaryData: new EncString(ENCRYPTED_TEXT), + encryptedApplicationData: new EncString(ENCRYPTED_TEXT), contentEncryptionKey: new EncString(ENCRYPTED_KEY), }); }); @@ -82,9 +119,9 @@ describe("RiskInsightsEncryptionService", () => { mockEncryptService.wrapSymmetricKey.mockResolvedValue(new EncString(ENCRYPTED_KEY)); // Act & Assert: call the method under test and expect rejection - await expect(service.encryptRiskInsightsReport(orgId, userId, testData)).rejects.toThrow( - "Encryption failed, encrypted strings are null", - ); + await expect( + service.encryptRiskInsightsReport({ organizationId: orgId, userId }, mockDecryptedData), + ).rejects.toThrow("Encryption failed, encrypted strings are null"); }); it("should throw an error when encrypted key is null or empty", async () => { @@ -94,18 +131,18 @@ describe("RiskInsightsEncryptionService", () => { mockEncryptService.wrapSymmetricKey.mockResolvedValue(new EncString("")); // Act & Assert: call the method under test and expect rejection - await expect(service.encryptRiskInsightsReport(orgId, userId, testData)).rejects.toThrow( - "Encryption failed, encrypted strings are null", - ); + await expect( + service.encryptRiskInsightsReport({ organizationId: orgId, userId }, mockDecryptedData), + ).rejects.toThrow("Encryption failed, encrypted strings are null"); }); it("should throw if org key is not found", async () => { // when we cannot get an organization key, we should throw an error mockKeyService.orgKeys$.mockReturnValue(new BehaviorSubject({})); - await expect(service.encryptRiskInsightsReport(orgId, userId, testData)).rejects.toThrow( - "Organization key not found", - ); + await expect( + service.encryptRiskInsightsReport({ organizationId: orgId, userId }, mockDecryptedData), + ).rejects.toThrow("Organization key not found"); }); }); @@ -120,23 +157,21 @@ describe("RiskInsightsEncryptionService", () => { // actual decryption does not happen here, // we just want to ensure the method calls are correct const result = await service.decryptRiskInsightsReport( - orgId, - userId, - new EncString("encrypted-data"), - new EncString("wrapped-key"), - (data) => data as typeof testData, + { organizationId: orgId, userId }, + mockEncryptedData, + mockKey, ); expect(mockKeyService.orgKeys$).toHaveBeenCalledWith(userId); - expect(mockEncryptService.unwrapSymmetricKey).toHaveBeenCalledWith( - new EncString("wrapped-key"), - orgKey, - ); - expect(mockEncryptService.decryptString).toHaveBeenCalledWith( - new EncString("encrypted-data"), - contentEncryptionKey, - ); - expect(result).toEqual(testData); + expect(mockEncryptService.unwrapSymmetricKey).toHaveBeenCalledWith(mockKey, orgKey); + expect(mockEncryptService.decryptString).toHaveBeenCalledTimes(3); + + // Mock decrypt returns JSON.stringify(testData) + expect(result).toEqual({ + reportData: testData, + summaryData: testData, + applicationData: testData, + }); }); it("should invoke data type validation method during decryption", async () => { @@ -144,77 +179,47 @@ describe("RiskInsightsEncryptionService", () => { mockKeyService.orgKeys$.mockReturnValue(orgKey$); mockEncryptService.unwrapSymmetricKey.mockResolvedValue(contentEncryptionKey); mockEncryptService.decryptString.mockResolvedValue(JSON.stringify(testData)); - const mockParseFn = jest.fn((data) => data as typeof testData); // act: call the decrypt method - with any params // actual decryption does not happen here, // we just want to ensure the method calls are correct const result = await service.decryptRiskInsightsReport( - orgId, - userId, - new EncString("encrypted-data"), - new EncString("wrapped-key"), - mockParseFn, + { organizationId: orgId, userId }, + mockEncryptedData, + mockKey, ); - expect(mockParseFn).toHaveBeenCalledWith(JSON.parse(JSON.stringify(testData))); - expect(result).toEqual(testData); + expect(result).toEqual({ + reportData: testData, + summaryData: testData, + applicationData: testData, + }); }); it("should return null if org key is not found", async () => { mockKeyService.orgKeys$.mockReturnValue(new BehaviorSubject({})); + await expect( + service.decryptRiskInsightsReport( + { organizationId: orgId, userId }, - const result = await service.decryptRiskInsightsReport( - orgId, - userId, - new EncString("encrypted-data"), - new EncString("wrapped-key"), - (data) => data as typeof testData, - ); - - expect(result).toBeNull(); + mockEncryptedData, + mockKey, + ), + ).rejects.toEqual(Error("Organization key not found")); }); it("should return null if decrypt throws", async () => { mockKeyService.orgKeys$.mockReturnValue(orgKey$); mockEncryptService.unwrapSymmetricKey.mockRejectedValue(new Error("fail")); - const result = await service.decryptRiskInsightsReport( - orgId, - userId, - new EncString("encrypted-data"), - new EncString("wrapped-key"), - (data) => data as typeof testData, - ); - expect(result).toBeNull(); - }); + await expect( + service.decryptRiskInsightsReport( + { organizationId: orgId, userId }, - it("should return null if decrypt throws", async () => { - mockKeyService.orgKeys$.mockReturnValue(orgKey$); - mockEncryptService.unwrapSymmetricKey.mockRejectedValue(new Error("fail")); - - const result = await service.decryptRiskInsightsReport( - orgId, - userId, - new EncString("encrypted-data"), - new EncString("wrapped-key"), - (data) => data as typeof testData, - ); - expect(result).toBeNull(); - }); - - it("should return null if decrypt throws", async () => { - mockKeyService.orgKeys$.mockReturnValue(orgKey$); - mockEncryptService.unwrapSymmetricKey.mockRejectedValue(new Error("fail")); - - const result = await service.decryptRiskInsightsReport( - orgId, - userId, - new EncString("encrypted-data"), - new EncString("wrapped-key"), - (data) => data as typeof testData, - ); - expect(result).toBeNull(); + mockEncryptedData, + mockKey, + ), + ).rejects.toEqual(Error("fail")); }); }); }); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.ts index 7bf01b04a63..04811f9cfcd 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-encryption.service.ts @@ -1,13 +1,13 @@ import { firstValueFrom, map } from "rxjs"; -import { Jsonify } from "type-fest"; import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { KeyService } from "@bitwarden/key-management"; -import { EncryptedDataWithKey } from "../models/password-health"; +import { DecryptedReportData, EncryptedReportData, EncryptedDataWithKey } from "../models"; export class RiskInsightsEncryptionService { constructor( @@ -16,11 +16,15 @@ export class RiskInsightsEncryptionService { private keyGeneratorService: KeyGenerationService, ) {} - async encryptRiskInsightsReport<T>( - organizationId: OrganizationId, - userId: UserId, - data: T, + async encryptRiskInsightsReport( + context: { + organizationId: OrganizationId; + userId: UserId; + }, + data: DecryptedReportData, + wrappedKey?: EncString, ): Promise<EncryptedDataWithKey> { + const { userId, organizationId } = context; const orgKey = await firstValueFrom( this.keyService .orgKeys$(userId) @@ -35,10 +39,28 @@ export class RiskInsightsEncryptionService { throw new Error("Organization key not found"); } - const contentEncryptionKey = await this.keyGeneratorService.createKey(512); + let contentEncryptionKey: SymmetricCryptoKey; + if (!wrappedKey) { + // Generate a new key + contentEncryptionKey = await this.keyGeneratorService.createKey(512); + } else { + // Unwrap the existing key + contentEncryptionKey = await this.encryptService.unwrapSymmetricKey(wrappedKey, orgKey); + } - const dataEncrypted = await this.encryptService.encryptString( - JSON.stringify(data), + const { reportData, summaryData, applicationData } = data; + + // Encrypt the data + const encryptedReportData = await this.encryptService.encryptString( + JSON.stringify(reportData), + contentEncryptionKey, + ); + const encryptedSummaryData = await this.encryptService.encryptString( + JSON.stringify(summaryData), + contentEncryptionKey, + ); + const encryptedApplicationData = await this.encryptService.encryptString( + JSON.stringify(applicationData), contentEncryptionKey, ); @@ -47,59 +69,87 @@ export class RiskInsightsEncryptionService { orgKey, ); - if (!dataEncrypted.encryptedString || !wrappedEncryptionKey.encryptedString) { + if ( + !encryptedReportData.encryptedString || + !encryptedSummaryData.encryptedString || + !encryptedApplicationData.encryptedString || + !wrappedEncryptionKey.encryptedString + ) { throw new Error("Encryption failed, encrypted strings are null"); } - const encryptedData = dataEncrypted; - const contentEncryptionKeyString = wrappedEncryptionKey; - const encryptedDataPacket: EncryptedDataWithKey = { organizationId, - encryptedData, - contentEncryptionKey: contentEncryptionKeyString, + encryptedReportData: encryptedReportData, + encryptedSummaryData: encryptedSummaryData, + encryptedApplicationData: encryptedApplicationData, + contentEncryptionKey: wrappedEncryptionKey, }; return encryptedDataPacket; } - async decryptRiskInsightsReport<T>( - organizationId: OrganizationId, - userId: UserId, - encryptedData: EncString, + async decryptRiskInsightsReport( + context: { + organizationId: OrganizationId; + userId: UserId; + }, + encryptedData: EncryptedReportData, wrappedKey: EncString, - parser: (data: Jsonify<T>) => T, - ): Promise<T | null> { - try { - const orgKey = await firstValueFrom( - this.keyService - .orgKeys$(userId) - .pipe( - map((organizationKeysById) => - organizationKeysById ? organizationKeysById[organizationId] : null, - ), + ): Promise<DecryptedReportData> { + const { userId, organizationId } = context; + const orgKey = await firstValueFrom( + this.keyService + .orgKeys$(userId) + .pipe( + map((organizationKeysById) => + organizationKeysById ? organizationKeysById[organizationId] : null, ), - ); + ), + ); - if (!orgKey) { - throw new Error("Organization key not found"); - } - - const unwrappedEncryptionKey = await this.encryptService.unwrapSymmetricKey( - wrappedKey, - orgKey, - ); - - const dataUnencrypted = await this.encryptService.decryptString( - encryptedData, - unwrappedEncryptionKey, - ); - - const dataUnencryptedJson = parser(JSON.parse(dataUnencrypted)); - - return dataUnencryptedJson as T; - } catch { - return null; + if (!orgKey) { + throw new Error("Organization key not found"); } + + const unwrappedEncryptionKey = await this.encryptService.unwrapSymmetricKey(wrappedKey, orgKey); + if (!unwrappedEncryptionKey) { + throw Error("Encryption key not found"); + } + + const { encryptedReportData, encryptedSummaryData, encryptedApplicationData } = encryptedData; + if (!encryptedReportData || !encryptedSummaryData || !encryptedApplicationData) { + throw new Error("Missing data"); + } + + // Decrypt the data + const decryptedReportData = await this.encryptService.decryptString( + encryptedReportData, + unwrappedEncryptionKey, + ); + const decryptedSummaryData = await this.encryptService.decryptString( + encryptedSummaryData, + unwrappedEncryptionKey, + ); + const decryptedApplicationData = await this.encryptService.decryptString( + encryptedApplicationData, + unwrappedEncryptionKey, + ); + + if (!decryptedReportData || !decryptedSummaryData || !decryptedApplicationData) { + throw new Error("Decryption failed, decrypted strings are null"); + } + + const decryptedReportDataJson = JSON.parse(decryptedReportData); + const decryptedSummaryDataJson = JSON.parse(decryptedSummaryData); + const decryptedApplicationDataJson = JSON.parse(decryptedApplicationData); + + const decryptedFullReport = { + reportData: decryptedReportDataJson, + summaryData: decryptedSummaryDataJson, + applicationData: decryptedApplicationDataJson, + }; + + return decryptedFullReport; } } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts index 18836fb1319..5f8fdaa244a 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts @@ -1,25 +1,23 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { makeEncString } from "@bitwarden/common/spec"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { createNewSummaryData } from "../helpers"; +import { DecryptedReportData, EncryptedDataWithKey } from "../models"; import { GetRiskInsightsReportResponse, SaveRiskInsightsReportResponse, } from "../models/api-models.types"; -import { EncryptedDataWithKey } from "../models/password-health"; import { - ApplicationHealthReportDetail, - OrganizationReportSummary, - RiskInsightsReportData, -} from "../models/report-models"; -import { MemberCipherDetailsResponse } from "../response/member-cipher-details.response"; + mockApplicationData, + mockCipherViews, + mockMemberDetails, + mockReportData, + mockSummaryData, +} from "../models/mock-data"; import { mockCiphers } from "./ciphers.mock"; import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; @@ -45,17 +43,13 @@ describe("RiskInsightsReportService", () => { // Non changing mock data const mockOrganizationId = "orgId" as OrganizationId; const mockUserId = "userId" as UserId; - const ENCRYPTED_TEXT = "This data has been encrypted"; - const ENCRYPTED_KEY = "Re-encrypted Cipher Key"; - const mockEncryptedText = new EncString(ENCRYPTED_TEXT); - const mockEncryptedKey = new EncString(ENCRYPTED_KEY); + const mockEncryptedKey = makeEncString("test-key"); // Changing mock data - let mockCipherViews: CipherView[]; - let mockMemberDetails: MemberCipherDetailsResponse[]; - let mockReport: ApplicationHealthReportDetail[]; - let mockSummary: OrganizationReportSummary; - let mockEncryptedReport: EncryptedDataWithKey; + let mockDecryptedData: DecryptedReportData; + const mockReportEnc = makeEncString(JSON.stringify(mockReportData)); + const mockSummaryEnc = makeEncString(JSON.stringify(mockSummaryData)); + const mockApplicationsEnc = makeEncString(JSON.stringify(mockApplicationData)); beforeEach(() => { cipherService.getAllFromApiForOrganization.mockResolvedValue(mockCiphers); @@ -87,75 +81,15 @@ describe("RiskInsightsReportService", () => { service = new RiskInsightsReportService( cipherService, memberCipherDetailsService, + mockPasswordHealthService, mockRiskInsightsApiService, mockRiskInsightsEncryptionService, - mockPasswordHealthService, ); - // Reset mock ciphers before each test - mockCipherViews = [ - mock<CipherView>({ - id: "cipher-1", - type: CipherType.Login, - login: { password: "pass1", username: "user1", uris: [{ uri: "https://app.com/login" }] }, - isDeleted: false, - viewPassword: true, - }), - mock<CipherView>({ - id: "cipher-2", - type: CipherType.Login, - login: { password: "pass2", username: "user2", uris: [{ uri: "app.com/home" }] }, - isDeleted: false, - viewPassword: true, - }), - mock<CipherView>({ - id: "cipher-3", - type: CipherType.Login, - login: { password: "pass3", username: "user3", uris: [{ uri: "https://other.com" }] }, - isDeleted: false, - viewPassword: true, - }), - ]; - mockMemberDetails = [ - mock<MemberCipherDetailsResponse>({ - cipherIds: ["cipher-1"], - userGuid: "user1", - userName: "User 1", - email: "user1@app.com", - }), - mock<MemberCipherDetailsResponse>({ - cipherIds: ["cipher-2"], - userGuid: "user2", - userName: "User 2", - email: "user2@app.com", - }), - mock<MemberCipherDetailsResponse>({ - cipherIds: ["cipher-3"], - userGuid: "user3", - userName: "User 3", - email: "user3@other.com", - }), - ]; - - mockReport = [ - { - applicationName: "app1", - passwordCount: 0, - atRiskPasswordCount: 0, - atRiskCipherIds: [], - memberCount: 0, - atRiskMemberCount: 0, - memberDetails: [], - atRiskMemberDetails: [], - cipherIds: [], - }, - ]; - mockSummary = createNewSummaryData(); - - mockEncryptedReport = { - organizationId: mockOrganizationId, - encryptedData: mockEncryptedText, - contentEncryptionKey: mockEncryptedKey, + mockDecryptedData = { + reportData: mockReportData, + summaryData: mockSummaryData, + applicationData: mockApplicationData, }; }); @@ -284,15 +218,22 @@ describe("RiskInsightsReportService", () => { describe("saveRiskInsightsReport$", () => { it("should not update subjects if save response does not have id", (done) => { + const mockEncryptedOutput: EncryptedDataWithKey = { + organizationId: mockOrganizationId, + encryptedReportData: mockReportEnc, + encryptedSummaryData: mockSummaryEnc, + encryptedApplicationData: mockApplicationsEnc, + contentEncryptionKey: mockEncryptedKey, + }; mockRiskInsightsEncryptionService.encryptRiskInsightsReport.mockResolvedValue( - mockEncryptedReport, + mockEncryptedOutput, ); const saveResponse = new SaveRiskInsightsReportResponse({ id: "" }); // Simulating no ID in response mockRiskInsightsApiService.saveRiskInsightsReport$.mockReturnValue(of(saveResponse)); service - .saveRiskInsightsReport$(mockReport, mockSummary, { + .saveRiskInsightsReport$(mockReportData, mockSummaryData, mockApplicationData, { organizationId: mockOrganizationId, userId: mockUserId, }) @@ -321,17 +262,19 @@ describe("RiskInsightsReportService", () => { it("should call with the correct organizationId", async () => { // we need to ensure that the api is invoked with the specified organizationId // here it doesn't matter what the Api returns - const apiResponse = { + const apiResponse = new GetRiskInsightsReportResponse({ id: "reportId", - date: new Date().toISOString(), + date: new Date(), organizationId: mockOrganizationId, - reportData: mockEncryptedReport.encryptedData, - contentEncryptionKey: mockEncryptedReport.contentEncryptionKey, - } as GetRiskInsightsReportResponse; + reportData: mockReportEnc.encryptedString, + summaryData: mockSummaryEnc.encryptedString, + applicationData: mockApplicationsEnc.encryptedString, + contentEncryptionKey: mockEncryptedKey.encryptedString, + }); - const decryptedResponse: RiskInsightsReportData = { - data: [], - summary: { + const decryptedResponse: DecryptedReportData = { + reportData: [], + summaryData: { totalMemberCount: 1, totalAtRiskMemberCount: 1, totalApplicationCount: 1, @@ -342,9 +285,9 @@ describe("RiskInsightsReportService", () => { totalCriticalAtRiskApplicationCount: 1, newApplications: [], }, + applicationData: [], }; - const organizationId = "orgId" as OrganizationId; const userId = "userId" as UserId; // Mock api returned encrypted data @@ -355,17 +298,15 @@ describe("RiskInsightsReportService", () => { Promise.resolve(decryptedResponse), ); - await firstValueFrom(service.getRiskInsightsReport$(organizationId, userId)); + await firstValueFrom(service.getRiskInsightsReport$(mockOrganizationId, userId)); expect(mockRiskInsightsApiService.getRiskInsightsReport$).toHaveBeenCalledWith( - organizationId, + mockOrganizationId, ); expect(mockRiskInsightsEncryptionService.decryptRiskInsightsReport).toHaveBeenCalledWith( - organizationId, - userId, - expect.anything(), // encryptedData - expect.anything(), // wrappedKey - expect.any(Function), // parser + { organizationId: mockOrganizationId, userId }, + expect.anything(), + expect.anything(), ); }); @@ -375,32 +316,29 @@ describe("RiskInsightsReportService", () => { const organizationId = "orgId" as OrganizationId; const userId = "userId" as UserId; - const mockResponse = { + const mockResponse = new GetRiskInsightsReportResponse({ id: "reportId", - date: new Date().toISOString(), + creationDate: new Date(), organizationId: organizationId as OrganizationId, - reportData: mockEncryptedReport.encryptedData, - contentEncryptionKey: mockEncryptedReport.contentEncryptionKey, - } as GetRiskInsightsReportResponse; + reportData: mockReportEnc.encryptedString, + summaryData: mockSummaryEnc.encryptedString, + applicationData: mockApplicationsEnc.encryptedString, + contentEncryptionKey: mockEncryptedKey.encryptedString, + }); - const decryptedReport = { - data: [{ foo: "bar" }], - }; mockRiskInsightsApiService.getRiskInsightsReport$.mockReturnValue(of(mockResponse)); mockRiskInsightsEncryptionService.decryptRiskInsightsReport.mockResolvedValue( - decryptedReport, + mockDecryptedData, ); const result = await firstValueFrom(service.getRiskInsightsReport$(organizationId, userId)); expect(mockRiskInsightsEncryptionService.decryptRiskInsightsReport).toHaveBeenCalledWith( - organizationId, - userId, + { organizationId: mockOrganizationId, userId }, expect.anything(), expect.anything(), - expect.any(Function), ); - expect(result).toEqual(decryptedReport); + expect(result).toEqual({ ...mockDecryptedData, creationDate: mockResponse.creationDate }); }); }); }); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index d82366c0154..c9a51e804dc 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -1,6 +1,7 @@ import { - BehaviorSubject, + catchError, concatMap, + EMPTY, first, firstValueFrom, forkJoin, @@ -19,7 +20,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { createNewReportData, - createNewSummaryData, flattenMemberDetails, getApplicationReportDetail, getFlattenedCipherDetails, @@ -45,7 +45,8 @@ import { CipherHealthReport, MemberDetails, PasswordHealthData, - RiskInsightsReportData, + OrganizationReportApplication, + RiskInsightsData, } from "../models/report-models"; import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; @@ -54,14 +55,6 @@ import { RiskInsightsApiService } from "./risk-insights-api.service"; import { RiskInsightsEncryptionService } from "./risk-insights-encryption.service"; export class RiskInsightsReportService { - private riskInsightsReportSubject = new BehaviorSubject<ApplicationHealthReportDetail[]>([]); - riskInsightsReport$ = this.riskInsightsReportSubject.asObservable(); - - private riskInsightsSummarySubject = new BehaviorSubject<OrganizationReportSummary>( - createNewSummaryData(), - ); - riskInsightsSummary$ = this.riskInsightsSummarySubject.asObservable(); - // [FIXME] CipherData // Cipher data // private _ciphersSubject = new BehaviorSubject<CipherView[] | null>(null); @@ -70,9 +63,9 @@ export class RiskInsightsReportService { constructor( private cipherService: CipherService, private memberCipherDetailsApiService: MemberCipherDetailsApiService, + private passwordHealthService: PasswordHealthService, private riskInsightsApiService: RiskInsightsApiService, private riskInsightsEncryptionService: RiskInsightsEncryptionService, - private passwordHealthService: PasswordHealthService, ) {} // [FIXME] CipherData @@ -152,6 +145,7 @@ export class RiskInsightsReportService { /** * Report data for the aggregation of uris to like uris and getting password/member counts, * members, and at risk statuses. + * * @param organizationId Id of the organization * @returns The all applications health report data */ @@ -235,17 +229,32 @@ export class RiskInsightsReportService { // TODO: totalCriticalMemberCount, totalCriticalAtRiskMemberCount, totalCriticalApplicationCount, totalCriticalAtRiskApplicationCount, and newApplications will be handled with future logic implementation return { totalMemberCount: uniqueMembers.length, - totalCriticalMemberCount: 0, totalAtRiskMemberCount: uniqueAtRiskMembers.length, - totalCriticalAtRiskMemberCount: 0, totalApplicationCount: reports.length, - totalCriticalApplicationCount: 0, totalAtRiskApplicationCount: reports.filter((app) => app.atRiskPasswordCount > 0).length, + totalCriticalMemberCount: 0, + totalCriticalAtRiskMemberCount: 0, + totalCriticalApplicationCount: 0, totalCriticalAtRiskApplicationCount: 0, newApplications: [], }; } + /** + * Generate a snapshot of applications and related data associated to this report + * + * @param reports + * @returns A list of applications with a critical marking flag + */ + generateOrganizationApplications( + reports: ApplicationHealthReportDetail[], + ): OrganizationReportApplication[] { + return reports.map((report) => ({ + applicationName: report.applicationName, + isCritical: false, + })); + } + async identifyCiphers( data: ApplicationHealthReportDetail[], organizationId: OrganizationId, @@ -272,12 +281,12 @@ export class RiskInsightsReportService { getRiskInsightsReport$( organizationId: OrganizationId, userId: UserId, - ): Observable<RiskInsightsReportData> { + ): Observable<RiskInsightsData> { return this.riskInsightsApiService.getRiskInsightsReport$(organizationId).pipe( - switchMap((response): Observable<RiskInsightsReportData> => { + switchMap((response) => { if (!response) { // Return an empty report and summary if response is falsy - return of<RiskInsightsReportData>(createNewReportData()); + return of<RiskInsightsData>(createNewReportData()); } if (!response.contentEncryptionKey || response.contentEncryptionKey.data == "") { return throwError(() => new Error("Report key not found")); @@ -285,15 +294,43 @@ export class RiskInsightsReportService { if (!response.reportData) { return throwError(() => new Error("Report data not found")); } + if (!response.summaryData) { + return throwError(() => new Error("Summary data not found")); + } + if (!response.applicationData) { + return throwError(() => new Error("Application data not found")); + } + return from( - this.riskInsightsEncryptionService.decryptRiskInsightsReport<RiskInsightsReportData>( - organizationId, - userId, - response.reportData, + this.riskInsightsEncryptionService.decryptRiskInsightsReport( + { + organizationId, + userId, + }, + { + encryptedReportData: response.reportData, + encryptedSummaryData: response.summaryData, + encryptedApplicationData: response.applicationData, + }, response.contentEncryptionKey, - (data) => data as RiskInsightsReportData, ), - ).pipe(map((decryptedReport) => decryptedReport ?? createNewReportData())); + ).pipe( + map((decryptedData) => ({ + reportData: decryptedData.reportData, + summaryData: decryptedData.summaryData, + applicationData: decryptedData.applicationData, + creationDate: response.creationDate, + })), + catchError((error: unknown) => { + // TODO Handle errors appropriately + // console.error("An error occurred when decrypting report", error); + return EMPTY; + }), + ); + }), + catchError((error: unknown) => { + // console.error("An error occurred when fetching the last report", error); + return EMPTY; }), ); } @@ -308,6 +345,7 @@ export class RiskInsightsReportService { saveRiskInsightsReport$( report: ApplicationHealthReportDetail[], summary: OrganizationReportSummary, + applications: OrganizationReportApplication[], encryptionParameters: { organizationId: OrganizationId; userId: UserId; @@ -315,28 +353,43 @@ export class RiskInsightsReportService { ): Observable<SaveRiskInsightsReportResponse> { return from( this.riskInsightsEncryptionService.encryptRiskInsightsReport( - encryptionParameters.organizationId, - encryptionParameters.userId, { - data: report, - summary: summary, + organizationId: encryptionParameters.organizationId, + userId: encryptionParameters.userId, + }, + { + reportData: report, + summaryData: summary, + applicationData: applications, }, ), ).pipe( - map(({ encryptedData, contentEncryptionKey }) => ({ - data: { - organizationId: encryptionParameters.organizationId, - date: new Date().toISOString(), - reportData: encryptedData.toSdk(), - contentEncryptionKey: contentEncryptionKey.toSdk(), - }, - })), + map( + ({ + encryptedReportData, + encryptedSummaryData, + encryptedApplicationData, + contentEncryptionKey, + }) => ({ + data: { + organizationId: encryptionParameters.organizationId, + creationDate: new Date().toISOString(), + reportData: encryptedReportData.toSdk(), + summaryData: encryptedSummaryData.toSdk(), + applicationData: encryptedApplicationData.toSdk(), + contentEncryptionKey: contentEncryptionKey.toSdk(), + }, + }), + ), switchMap((encryptedReport) => this.riskInsightsApiService.saveRiskInsightsReport$( encryptedReport, encryptionParameters.organizationId, ), ), + catchError((error: unknown) => { + return EMPTY; + }), map((response) => { if (!isSaveRiskInsightsReportResponse(response)) { throw new Error("Invalid response from API"); @@ -457,6 +510,13 @@ export class RiskInsightsReportService { const applicationMap = new Map<string, CipherHealthReport[]>(); cipherHealthData.forEach((cipher: CipherHealthReport) => { + // Warning: Currently does not show ciphers with NO Application + // if (cipher.applications.length === 0) { + // const existingApplication = applicationMap.get("None") || []; + // existingApplication.push(cipher); + // applicationMap.set("None", existingApplication); + // } + cipher.applications.forEach((application) => { const existingApplication = applicationMap.get(application) || []; existingApplication.push(cipher); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts index 01bf19f30a4..657f76d414b 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts @@ -29,28 +29,32 @@ import { RiskInsightsComponent } from "./risk-insights.component"; @NgModule({ imports: [RiskInsightsComponent, AccessIntelligenceRoutingModule], providers: [ - { + safeProvider({ provide: MemberCipherDetailsApiService, + useClass: MemberCipherDetailsApiService, deps: [ApiService], - }, - { + }), + safeProvider({ provide: PasswordHealthService, + useClass: PasswordHealthService, deps: [PasswordStrengthServiceAbstraction, AuditService], - }, - { + }), + safeProvider({ provide: RiskInsightsApiService, + useClass: RiskInsightsApiService, deps: [ApiService], - }, - { + }), + safeProvider({ provide: RiskInsightsReportService, + useClass: RiskInsightsReportService, deps: [ CipherService, MemberCipherDetailsApiService, + PasswordHealthService, RiskInsightsApiService, RiskInsightsEncryptionService, - PasswordHealthService, ], - }, + }), safeProvider({ provide: RiskInsightsDataService, deps: [ @@ -78,7 +82,7 @@ import { RiskInsightsComponent } from "./risk-insights.component"; safeProvider({ provide: AllActivitiesService, useClass: AllActivitiesService, - deps: [], + deps: [RiskInsightsDataService], }), safeProvider({ provide: SecurityTasksApiService, diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index a1b5611ff14..390102aced9 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -1,10 +1,6 @@ -@if (isLoading$ | async) { - <div *ngIf="isLoading$ | async"> - <tools-risk-insights-loading></tools-risk-insights-loading> - </div> -} - -@if (!(isLoading$ | async)) { +@if (dataService.isLoading$ | async) { + <dirt-risk-insights-loading></dirt-risk-insights-loading> +} @else { <ul class="tw-inline-grid tw-grid-cols-3 tw-gap-6 tw-m-0 tw-p-0 tw-w-full tw-auto-cols-auto tw-list-none" > diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts index 1691e35c819..83ce743d6d0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts @@ -20,7 +20,7 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" import { RiskInsightsTabType } from "./risk-insights.component"; @Component({ - selector: "tools-all-activity", + selector: "dirt-all-activity", imports: [ ApplicationsLoadingComponent, SharedModule, @@ -30,7 +30,6 @@ import { RiskInsightsTabType } from "./risk-insights.component"; templateUrl: "./all-activity.component.html", }) export class AllActivityComponent implements OnInit { - protected isLoading$ = this.dataService.isLoading$; organization: Organization | null = null; totalCriticalAppsAtRiskMemberCount = 0; totalCriticalAppsCount = 0; diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html index febdb5fa0de..1971b61d516 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html @@ -1,96 +1,102 @@ -<div *ngIf="isLoading$ | async"> - <tools-risk-insights-loading></tools-risk-insights-loading> -</div> -<div class="tw-mt-4" *ngIf="!(isLoading$ | async) && !dataSource.data.length"> - <bit-no-items [icon]="noItemsIcon" class="tw-text-main"> - <ng-container slot="title"> - <h2 class="tw-font-semibold tw-mt-4"> - {{ "noAppsInOrgTitle" | i18n: organization?.name }} - </h2> - </ng-container> - <ng-container slot="description"> - <div class="tw-flex tw-flex-col tw-mb-2"> - <span class="tw-text-muted"> - {{ "noAppsInOrgDescription" | i18n }} - </span> - <a class="tw-text-primary-600" routerLink="/login">{{ "learnMore" | i18n }}</a> +@if (dataService.isLoading$ | async) { + <dirt-risk-insights-loading></dirt-risk-insights-loading> +} @else { + @let drawerDetails = dataService.drawerDetails$ | async; + @if (!dataSource.data.length) { + <div class="tw-mt-4"> + <bit-no-items [icon]="noItemsIcon" class="tw-text-main"> + <ng-container slot="title"> + <h2 class="tw-font-semibold tw-mt-4"> + {{ + "noAppsInOrgTitle" + | i18n: (dataService.organizationDetails$ | async)?.organizationName || "" + }} + </h2> + </ng-container> + <ng-container slot="description"> + <div class="tw-flex tw-flex-col tw-mb-2"> + <span class="tw-text-muted"> + {{ "noAppsInOrgDescription" | i18n }} + </span> + <a class="tw-text-primary-600" routerLink="/login">{{ "learnMore" | i18n }}</a> + </div> + </ng-container> + <ng-container slot="button"> + <button (click)="goToCreateNewLoginItem()" bitButton buttonType="primary" type="button"> + {{ "createNewLoginItem" | i18n }} + </button> + </ng-container> + </bit-no-items> + </div> + } @else { + <div class="tw-mt-4 tw-flex tw-flex-col"> + <h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2> + <div class="tw-flex tw-gap-6"> + <button + type="button" + class="tw-flex-1" + tabindex="0" + (click)="dataService.setDrawerForOrgAtRiskMembers('allAppsOrgAtRiskMembers')" + > + <dirt-card + #allAppsOrgAtRiskMembers + class="tw-w-full" + [ngClass]="{ + 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers', + }" + [title]="'atRiskMembers' | i18n" + [value]="applicationSummary.totalAtRiskMemberCount" + [maxValue]="applicationSummary.totalMemberCount" + > + </dirt-card> + </button> + <button + type="button" + class="tw-flex-1" + tabindex="0" + (click)="dataService.setDrawerForOrgAtRiskApps('allAppsOrgAtRiskApplications')" + > + <dirt-card + #allAppsOrgAtRiskApplications + class="tw-w-full" + [ngClass]="{ + 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications', + }" + [title]="'atRiskApplications' | i18n" + [value]="applicationSummary.totalAtRiskApplicationCount" + [maxValue]="applicationSummary.totalApplicationCount" + > + </dirt-card> + </button> </div> - </ng-container> - <ng-container slot="button"> - <button (click)="goToCreateNewLoginItem()" bitButton buttonType="primary" type="button"> - {{ "createNewLoginItem" | i18n }} - </button> - </ng-container> - </bit-no-items> -</div> -<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!(isLoading$ | async) && dataSource.data.length"> - <h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2> - @if (dataService.drawerDetails$ | async; as drawerDetails) { - <div class="tw-flex tw-gap-6"> - <button - type="button" - class="tw-flex-1" - tabindex="0" - (click)="showOrgAtRiskMembers('allAppsOrgAtRiskMembers')" - > - <dirt-card - #allAppsOrgAtRiskMembers - class="tw-w-full" - [ngClass]="{ - 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers', - }" - [title]="'atRiskMembers' | i18n" - [value]="applicationSummary.totalAtRiskMemberCount" - [maxValue]="applicationSummary.totalMemberCount" + <div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4"> + <bit-search + [placeholder]="'searchApps' | i18n" + class="tw-grow" + [formControl]="searchControl" + ></bit-search> + <button + type="button" + [buttonType]="'primary'" + bitButton + [disabled]="!selectedUrls.size" + [loading]="markingAsCritical" + (click)="markAppsAsCritical()" > - </dirt-card> - </button> - <button - type="button" - class="tw-flex-1" - tabindex="0" - (click)="showOrgAtRiskApps('allAppsOrgAtRiskApplications')" - > - <dirt-card - #allAppsOrgAtRiskApplications - class="tw-w-full" - [ngClass]="{ - 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications', - }" - [title]="'atRiskApplications' | i18n" - [value]="applicationSummary.totalAtRiskApplicationCount" - [maxValue]="applicationSummary.totalApplicationCount" - > - </dirt-card> - </button> - </div> - <div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4"> - <bit-search - [placeholder]="'searchApps' | i18n" - class="tw-grow" - [formControl]="searchControl" - ></bit-search> - <button - type="button" - [buttonType]="'primary'" - bitButton - [disabled]="!selectedUrls.size" - [loading]="markingAsCritical" - (click)="markAppsAsCritical()" - > - <i class="bwi tw-mr-2" [ngClass]="selectedUrls.size ? 'bwi-star-f' : 'bwi-star'"></i> - {{ "markAppAsCritical" | i18n }} - </button> - </div> + <i class="bwi tw-mr-2" [ngClass]="selectedUrls.size ? 'bwi-star-f' : 'bwi-star'"></i> + {{ "markAppAsCritical" | i18n }} + </button> + </div> - <app-table-row-scrollable - [dataSource]="dataSource" - [showRowCheckBox]="true" - [showRowMenuForCriticalApps]="false" - [selectedUrls]="selectedUrls" - [openApplication]="drawerDetails.invokerId || ''" - [checkboxChange]="onCheckboxChange" - [showAppAtRiskMembers]="showAppAtRiskMembers" - ></app-table-row-scrollable> + <app-table-row-scrollable + [dataSource]="dataSource" + [showRowCheckBox]="true" + [showRowMenuForCriticalApps]="false" + [selectedUrls]="selectedUrls" + [openApplication]="drawerDetails.invokerId || ''" + [checkboxChange]="onCheckboxChange" + [showAppAtRiskMembers]="showAppAtRiskMembers" + ></app-table-row-scrollable> + </div> } -</div> +} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index 3b7490dbc19..bc04884c799 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -2,33 +2,17 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { combineLatest, debounceTime, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; +import { debounceTime } from "rxjs"; import { Security } from "@bitwarden/assets/svg"; import { - AllActivitiesService, - CriticalAppsService, + ApplicationHealthReportDetailEnriched, RiskInsightsDataService, - RiskInsightsReportService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers"; -import { - LEGACY_ApplicationHealthReportDetailWithCriticalFlag, - LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher, -} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models"; -import { RiskInsightsEncryptionService } from "@bitwarden/bit-common/dirt/reports/risk-insights/services/risk-insights-encryption.service"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { OrganizationId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { IconButtonModule, NoItemsModule, @@ -45,7 +29,7 @@ import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.compo import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @Component({ - selector: "tools-all-applications", + selector: "dirt-all-applications", templateUrl: "./all-applications.component.html", imports: [ ApplicationsLoadingComponent, @@ -60,97 +44,44 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" ], }) export class AllApplicationsComponent implements OnInit { - protected dataSource = - new TableDataSource<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher>(); + protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>(); protected selectedUrls: Set<string> = new Set<string>(); protected searchControl = new FormControl("", { nonNullable: true }); - protected loading = true; protected organization = new Organization(); noItemsIcon = Security; protected markingAsCritical = false; protected applicationSummary: OrganizationReportSummary = createNewSummaryData(); destroyRef = inject(DestroyRef); - isLoading$: Observable<boolean> = of(false); - - async ngOnInit() { - const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - - if (organizationId) { - const organization$ = this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(organizationId)); - - combineLatest([ - this.dataService.applications$, - this.criticalAppsService.getAppsListForOrg(organizationId as OrganizationId), - organization$, - ]) - .pipe( - takeUntilDestroyed(this.destroyRef), - map(([applications, criticalApps, organization]) => { - if (applications && applications.length === 0 && criticalApps && criticalApps) { - const criticalUrls = criticalApps.map((ca) => ca.uri); - const data = applications?.map((app) => ({ - ...app, - isMarkedAsCritical: criticalUrls.includes(app.applicationName), - })) as LEGACY_ApplicationHealthReportDetailWithCriticalFlag[]; - return { data, organization }; - } - - return { data: applications, organization }; - }), - switchMap(async ({ data, organization }) => { - if (data && organization) { - const dataWithCiphers = await this.reportService.identifyCiphers( - data, - organization.id as OrganizationId, - ); - - return { - data: dataWithCiphers, - organization, - }; - } - - return { data: [], organization }; - }), - ) - .subscribe(({ data, organization }) => { - if (data) { - this.dataSource.data = data; - this.applicationSummary = this.reportService.generateApplicationsSummary(data); - this.allActivitiesService.setAllAppsReportSummary(this.applicationSummary); - } - if (organization) { - this.organization = organization; - } - }); - - this.isLoading$ = this.dataService.isLoading$; - } - } constructor( - protected cipherService: CipherService, protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, protected toastService: ToastService, - protected configService: ConfigService, protected dataService: RiskInsightsDataService, - protected organizationService: OrganizationService, - protected reportService: RiskInsightsReportService, - private accountService: AccountService, - protected criticalAppsService: CriticalAppsService, - protected riskInsightsEncryptionService: RiskInsightsEncryptionService, - protected allActivitiesService: AllActivitiesService, + // protected allActivitiesService: AllActivitiesService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) .subscribe((v) => (this.dataSource.filter = v)); } + async ngOnInit() { + this.dataService.reportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ + next: (report) => { + this.applicationSummary = report?.summaryData ?? createNewSummaryData(); + this.dataSource.data = report?.reportData ?? []; + }, + error: () => { + this.dataSource.data = []; + }, + }); + + // TODO + // this.applicationSummary = this.reportService.generateApplicationsSummary(data); + // this.allActivitiesService.setAllAppsReportSummary(this.applicationSummary); + } + goToCreateNewLoginItem = async () => { // TODO: implement this.toastService.showToast({ @@ -167,41 +98,31 @@ export class AllApplicationsComponent implements OnInit { markAppsAsCritical = async () => { this.markingAsCritical = true; - try { - await this.criticalAppsService.setCriticalApps( - this.organization.id as OrganizationId, - Array.from(this.selectedUrls), - ); - - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("applicationsMarkedAsCriticalSuccess"), + this.dataService + .saveCriticalApplications(Array.from(this.selectedUrls)) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: () => { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("applicationsMarkedAsCriticalSuccess"), + }); + this.selectedUrls.clear(); + this.markingAsCritical = false; + }, + error: () => { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("applicationsMarkedAsCriticalFail"), + }); + }, }); - } finally { - this.selectedUrls.clear(); - this.markingAsCritical = false; - } }; showAppAtRiskMembers = async (applicationName: string) => { - const info = { - members: - this.dataSource.data.find((app) => app.applicationName === applicationName) - ?.atRiskMemberDetails ?? [], - applicationName, - }; - this.dataService.setDrawerForAppAtRiskMembers(info, applicationName); - }; - - showOrgAtRiskMembers = async (invokerId: string) => { - const dialogData = this.reportService.generateAtRiskMemberList(this.dataSource.data); - this.dataService.setDrawerForOrgAtRiskMembers(dialogData, invokerId); - }; - - showOrgAtRiskApps = async (invokerId: string) => { - const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data); - this.dataService.setDrawerForOrgAtRiskApps(data, invokerId); + await this.dataService.setDrawerForAppAtRiskMembers(applicationName); }; onCheckboxChange = (applicationName: string, event: Event) => { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts index 01f3b8fb494..e34b13176ee 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; +import { ApplicationHealthReportDetailEnriched } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @@ -14,7 +14,7 @@ import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pip }) export class AppTableRowScrollableComponent { @Input() - dataSource!: TableDataSource<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher>; + dataSource!: TableDataSource<ApplicationHealthReportDetailEnriched>; @Input() showRowMenuForCriticalApps: boolean = false; @Input() showRowCheckBox: boolean = false; @Input() selectedUrls: Set<string> = new Set<string>(); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html index ed2a6b96524..cfcdf3a1841 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html @@ -49,7 +49,7 @@ type="button" class="tw-flex-1" tabindex="0" - (click)="showOrgAtRiskMembers('criticalAppsAtRiskMembers')" + (click)="dataService.setDrawerForOrgAtRiskMembers('criticalAppsAtRiskMembers')" > <dirt-card #criticalAppsAtRiskMembers @@ -67,7 +67,7 @@ type="button" class="tw-flex-1" tabindex="0" - (click)="showOrgAtRiskApps('criticalAppsAtRiskApplications')" + (click)="dataService.setDrawerForOrgAtRiskApps('criticalAppsAtRiskApplications')" > <dirt-card #criticalAppsAtRiskApplications @@ -96,7 +96,7 @@ [showRowMenuForCriticalApps]="true" [openApplication]="drawerDetails.invokerId || ''" [showAppAtRiskMembers]="showAppAtRiskMembers" - [unmarkAsCritical]="unmarkAsCritical" + [unmarkAsCritical]="removeCriticalApplication" ></app-table-row-scrollable> } </div> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts index 7848d37ea94..6a0ac5c2883 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts @@ -4,23 +4,15 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, debounceTime, firstValueFrom, map, switchMap } from "rxjs"; +import { debounceTime, EMPTY, map, switchMap } from "rxjs"; import { Security } from "@bitwarden/assets/svg"; import { - AllActivitiesService, - CriticalAppsService, + ApplicationHealthReportDetailEnriched, RiskInsightsDataService, - RiskInsightsReportService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { - LEGACY_ApplicationHealthReportDetailWithCriticalFlag, - LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher, -} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; +import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers"; import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; import { SecurityTaskType } from "@bitwarden/common/vault/tasks"; @@ -37,7 +29,7 @@ import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.compo import { RiskInsightsTabType } from "./risk-insights.component"; @Component({ - selector: "tools-critical-applications", + selector: "dirt-critical-applications", templateUrl: "./critical-applications.component.html", imports: [ CardComponent, @@ -51,60 +43,57 @@ import { RiskInsightsTabType } from "./risk-insights.component"; providers: [DefaultAdminTaskService], }) export class CriticalApplicationsComponent implements OnInit { - protected dataSource = - new TableDataSource<LEGACY_ApplicationHealthReportDetailWithCriticalFlagAndCipher>(); - protected selectedIds: Set<number> = new Set<number>(); - protected searchControl = new FormControl("", { nonNullable: true }); private destroyRef = inject(DestroyRef); protected loading = false; + protected enableRequestPasswordChange = false; protected organizationId: OrganizationId; - protected applicationSummary = {} as OrganizationReportSummary; noItemsIcon = Security; - enableRequestPasswordChange = false; + + protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>(); + protected applicationSummary = {} as OrganizationReportSummary; + + protected selectedIds: Set<number> = new Set<number>(); + protected searchControl = new FormControl("", { nonNullable: true }); + + constructor( + protected activatedRoute: ActivatedRoute, + protected router: Router, + protected toastService: ToastService, + protected dataService: RiskInsightsDataService, + protected i18nService: I18nService, + private adminTaskService: DefaultAdminTaskService, + ) { + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((v) => (this.dataSource.filter = v)); + } async ngOnInit() { - this.organizationId = this.activatedRoute.snapshot.paramMap.get( - "organizationId", - ) as OrganizationId; - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.criticalAppsService.loadOrganizationContext(this.organizationId as OrganizationId, userId); - - if (this.organizationId) { - combineLatest([ - this.dataService.applications$, - this.criticalAppsService.getAppsListForOrg(this.organizationId as OrganizationId), - ]) - .pipe( - takeUntilDestroyed(this.destroyRef), - map(([applications, criticalApps]) => { - const criticalUrls = criticalApps.map((ca) => ca.uri); - const data = applications?.map((app) => ({ - ...app, - isMarkedAsCritical: criticalUrls.includes(app.applicationName), - })) as LEGACY_ApplicationHealthReportDetailWithCriticalFlag[]; - return data?.filter((app) => app.isMarkedAsCritical); - }), - switchMap(async (data) => { - if (data) { - const dataWithCiphers = await this.reportService.identifyCiphers( - data, - this.organizationId, - ); - return dataWithCiphers; - } - return null; - }), - ) - .subscribe((applications) => { - if (applications) { - this.dataSource.data = applications; - this.applicationSummary = this.reportService.generateApplicationsSummary(applications); - this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0; - this.allActivitiesService.setCriticalAppsReportSummary(this.applicationSummary); - this.allActivitiesService.setAllAppsReportDetails(applications); + this.dataService.criticalReportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ + next: (criticalReport) => { + this.dataSource.data = criticalReport?.reportData ?? []; + this.applicationSummary = criticalReport?.summaryData ?? createNewSummaryData(); + this.enableRequestPasswordChange = criticalReport?.summaryData?.totalAtRiskMemberCount > 0; + }, + error: () => { + this.dataSource.data = []; + this.applicationSummary = createNewSummaryData(); + this.enableRequestPasswordChange = false; + }, + }); + this.activatedRoute.paramMap + .pipe( + takeUntilDestroyed(this.destroyRef), + map((params) => params.get("organizationId")), + switchMap(async (orgId) => { + if (orgId) { + this.organizationId = orgId as OrganizationId; + } else { + return EMPTY; } - }); - } + }), + ) + .subscribe(); } goToAllAppsTab = async () => { @@ -117,26 +106,25 @@ export class CriticalApplicationsComponent implements OnInit { ); }; - unmarkAsCritical = async (hostname: string) => { - try { - await this.criticalAppsService.dropCriticalApp( - this.organizationId as OrganizationId, - hostname, - ); - } catch { - this.toastService.showToast({ - message: this.i18nService.t("unexpectedError"), - variant: "error", - title: this.i18nService.t("error"), + removeCriticalApplication = async (hostname: string) => { + this.dataService + .removeCriticalApplication(hostname) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: () => { + this.toastService.showToast({ + message: this.i18nService.t("criticalApplicationUnmarkedSuccessfully"), + variant: "success", + }); + }, + error: () => { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + variant: "error", + title: this.i18nService.t("error"), + }); + }, }); - return; - } - - this.toastService.showToast({ - message: this.i18nService.t("criticalApplicationUnmarkedSuccessfully"), - variant: "success", - }); - this.dataSource.data = this.dataSource.data.filter((app) => app.applicationName !== hostname); }; async requestPasswordChange() { @@ -167,42 +155,7 @@ export class CriticalApplicationsComponent implements OnInit { }); } } - - constructor( - protected activatedRoute: ActivatedRoute, - protected router: Router, - protected toastService: ToastService, - protected dataService: RiskInsightsDataService, - protected criticalAppsService: CriticalAppsService, - protected reportService: RiskInsightsReportService, - protected i18nService: I18nService, - private configService: ConfigService, - private adminTaskService: DefaultAdminTaskService, - private accountService: AccountService, - private allActivitiesService: AllActivitiesService, - ) { - this.searchControl.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) - .subscribe((v) => (this.dataSource.filter = v)); - } - showAppAtRiskMembers = async (applicationName: string) => { - const data = { - members: - this.dataSource.data.find((app) => app.applicationName === applicationName) - ?.atRiskMemberDetails ?? [], - applicationName, - }; - this.dataService.setDrawerForAppAtRiskMembers(data, applicationName); - }; - - showOrgAtRiskMembers = async (invokerId: string) => { - const data = this.reportService.generateAtRiskMemberList(this.dataSource.data); - this.dataService.setDrawerForOrgAtRiskMembers(data, invokerId); - }; - - showOrgAtRiskApps = async (invokerId: string) => { - const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data); - this.dataService.setDrawerForOrgAtRiskApps(data, invokerId); + await this.dataService.setDrawerForAppAtRiskMembers(applicationName); }; } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts index af61c9a35c8..1d18ca3a030 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts @@ -4,7 +4,7 @@ import { Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @Component({ - selector: "tools-risk-insights-loading", + selector: "dirt-risk-insights-loading", imports: [CommonModule, JslibModule], templateUrl: "./risk-insights-loading.component.html", }) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index 50af2c9e9a7..49ccfb73c5d 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -4,19 +4,23 @@ {{ "reviewAtRiskPasswords" | i18n }} </div> <div - *ngIf="dataLastUpdated$ | async" class="tw-bg-primary-100 tw-rounded-lg tw-w-full tw-px-8 tw-py-4 tw-my-4 tw-flex tw-items-center" > <i class="bwi bwi-exclamation-triangle bwi-lg tw-text-[1.2rem] tw-text-muted" aria-hidden="true" ></i> - <span class="tw-mx-4">{{ - "dataLastUpdated" | i18n: (dataLastUpdated$ | async | date: "MMMM d, y 'at' h:mm a") - }}</span> - <span class="tw-flex tw-justify-center tw-w-16"> + @if (dataLastUpdated) { + <span class="tw-mx-4">{{ + "dataLastUpdated" | i18n: (dataLastUpdated | date: "MMMM d, y 'at' h:mm a") + }}</span> + } @else { + <span class="tw-mx-4">{{ "noReportRan" | i18n }}</span> + } + @let isRunningReport = dataService.isRunningReport$ | async; + <span class="tw-flex tw-justify-center"> <button - *ngIf="!(isRefreshing$ | async)" + *ngIf="!isRunningReport" type="button" bitButton buttonType="secondary" @@ -24,11 +28,11 @@ tabindex="0" [bitAction]="refreshData.bind(this)" > - {{ "refresh" | i18n }} + {{ "riskInsightsRunReport" | i18n }} </button> <span> <i - *ngIf="isRefreshing$ | async" + *ngIf="isRunningReport" class="bwi bwi-spinner bwi-spin tw-text-muted tw-text-[1.2rem]" aria-hidden="true" ></i> @@ -38,18 +42,21 @@ <bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)"> @if (isRiskInsightsActivityTabFeatureEnabled) { <bit-tab label="{{ 'activity' | i18n }}"> - <tools-all-activity></tools-all-activity> + <dirt-all-activity></dirt-all-activity> </bit-tab> } <bit-tab label="{{ 'allApplicationsWithCount' | i18n: appsCount }}"> - <tools-all-applications></tools-all-applications> + <dirt-all-applications></dirt-all-applications> </bit-tab> <bit-tab> <ng-template bitTabLabel> <i class="bwi bwi-star"></i> - {{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }} + {{ + "criticalApplicationsWithCount" + | i18n: (dataService.criticalReportResults$ | async)?.reportData?.length ?? 0 + }} </ng-template> - <tools-critical-applications></tools-critical-applications> + <dirt-critical-applications></dirt-critical-applications> </bit-tab> </bit-tab-group> @@ -69,7 +76,9 @@ }}</span> <ng-container *ngIf="drawerDetails.atRiskMemberDetails.length > 0"> <div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted"> - <div bitTypography="body2" class="tw-text-sm tw-font-bold">{{ "email" | i18n }}</div> + <div bitTypography="body2" class="tw-text-sm tw-font-bold"> + {{ "email" | i18n }} + </div> <div bitTypography="body2" class="tw-text-sm tw-font-bold"> {{ "atRiskPasswords" | i18n }} </div> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index 208ba59fb9d..308cc351dc3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -2,21 +2,12 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit, inject } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { EMPTY, firstValueFrom, Observable } from "rxjs"; +import { EMPTY } from "rxjs"; import { map, switchMap } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - CriticalAppsService, - RiskInsightsDataService, -} from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { PasswordHealthReportApplicationsResponse } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/api-models.types"; -import { - ApplicationHealthReportDetail, - DrawerType, -} from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { DrawerType } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; @@ -67,19 +58,13 @@ export class RiskInsightsComponent implements OnInit { tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps; isRiskInsightsActivityTabFeatureEnabled: boolean = false; - dataLastUpdated: Date = new Date(); - - criticalApps$: Observable<PasswordHealthReportApplicationsResponse[]> = new Observable(); - appsCount: number = 0; - criticalAppsCount: number = 0; - notifiedMembersCount: number = 0; + // Leaving this commented because it's not used but seems important + // notifiedMembersCount: number = 0; private organizationId: OrganizationId = "" as OrganizationId; - isLoading$: Observable<boolean> = new Observable<boolean>(); - isRefreshing$: Observable<boolean> = new Observable<boolean>(); - dataLastUpdated$: Observable<Date | null> = new Observable<Date | null>(); + dataLastUpdated: Date | null = null; refetching: boolean = false; constructor( @@ -87,8 +72,6 @@ export class RiskInsightsComponent implements OnInit { private router: Router, private configService: ConfigService, protected dataService: RiskInsightsDataService, - private criticalAppsService: CriticalAppsService, - private accountService: AccountService, ) { this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps; @@ -104,39 +87,29 @@ export class RiskInsightsComponent implements OnInit { } async ngOnInit() { - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.route.paramMap .pipe( takeUntilDestroyed(this.destroyRef), map((params) => params.get("organizationId")), - switchMap((orgId) => { + switchMap(async (orgId) => { if (orgId) { + // Initialize Data Service + await this.dataService.initializeForOrganization(orgId as OrganizationId); + this.organizationId = orgId as OrganizationId; - this.dataService.fetchApplicationsReport(this.organizationId); - this.isLoading$ = this.dataService.isLoading$; - this.isRefreshing$ = this.dataService.isRefreshing$; - this.dataLastUpdated$ = this.dataService.dataLastUpdated$; - return this.dataService.applications$; } else { return EMPTY; } }), ) - .subscribe({ - next: (applications: ApplicationHealthReportDetail[] | null) => { - if (applications) { - this.appsCount = applications.length; - } + .subscribe(); - this.criticalAppsService.loadOrganizationContext( - this.organizationId as OrganizationId, - userId, - ); - this.criticalApps$ = this.criticalAppsService.getAppsListForOrg( - this.organizationId as OrganizationId, - ); - }, + // Subscribe to report result details + this.dataService.reportResults$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((report) => { + this.appsCount = report?.reportData.length ?? 0; + this.dataLastUpdated = report?.creationDate ?? null; }); // Subscribe to drawer state changes @@ -156,7 +129,7 @@ export class RiskInsightsComponent implements OnInit { */ refreshData(): void { if (this.organizationId) { - this.dataService.refreshApplicationsReport(this.organizationId); + this.dataService.triggerReport(); } } From f073fde44374aabb7fdb1adad442dac49b8233e6 Mon Sep 17 00:00:00 2001 From: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:26:55 +0200 Subject: [PATCH 71/83] [PM-22454] Autofill correct login form from extension (#16680) * [PM-22454] Autofill correct login form from extension * Remove redundant setting of `f` * Remove correct redundant call --- .../src/autofill/services/autofill.service.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index ca735f8f4f3..73262962dbc 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -2270,6 +2270,8 @@ export default class AutofillService implements AutofillServiceInterface { withoutForm: boolean, ): AutofillField | null { let usernameField: AutofillField = null; + let usernameFieldInSameForm: AutofillField = null; + for (let i = 0; i < pageDetails.fields.length; i++) { const f = pageDetails.fields[i]; if (AutofillService.forCustomFieldsOnly(f)) { @@ -2282,22 +2284,29 @@ export default class AutofillService implements AutofillServiceInterface { const includesUsernameFieldName = this.findMatchingFieldIndex(f, AutoFillConstants.UsernameFieldNames) > -1; + const isInSameForm = f.form === passwordField.form; if ( !f.disabled && (canBeReadOnly || !f.readonly) && - (withoutForm || f.form === passwordField.form || includesUsernameFieldName) && + (withoutForm || isInSameForm || includesUsernameFieldName) && (canBeHidden || f.viewable) && (f.type === "text" || f.type === "email" || f.type === "tel") ) { - usernameField = f; - // We found an exact match. No need to keep looking. - if (includesUsernameFieldName) { - break; + // Prioritize fields in the same form as the password field + if (isInSameForm) { + usernameFieldInSameForm = f; + if (includesUsernameFieldName) { + return f; + } + } else { + usernameField = f; } } } - return usernameField; + + // Prefer username field in same form, fall back to any username field + return usernameFieldInSameForm || usernameField; } /** From 5260c8336bd4ef5e49029659b14c0b39fd2533a5 Mon Sep 17 00:00:00 2001 From: Joseph Robinson <166070458+Robinson-Joseph@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:13:43 -0400 Subject: [PATCH 72/83] Makes a small change to center the window of the desktop app when bounds change ([#2617](https://github.com/bitwarden/clients/issues/2617)) (#16595) This is my first commit, and I don't believe this would count as a major UX change so I have not made a forum post. --- apps/desktop/src/main/window.main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 1595252251b..993084f7724 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -500,9 +500,9 @@ export class WindowMain { displayBounds.x !== state.displayBounds.x || displayBounds.y !== state.displayBounds.y ) { - state.x = undefined; - state.y = undefined; displayBounds = screen.getPrimaryDisplay().bounds; + state.x = displayBounds.x + displayBounds.width / 2 - state.width / 2; + state.y = displayBounds.y + displayBounds.height / 2 - state.height / 2; } } From f29e5e223d783d34f8b533b631d63f8eb73e12ad Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:29:59 -0400 Subject: [PATCH 73/83] [PM-26185] new app metric card (#16658) * new messages.json keys * button changes for dirt activity card * dummy data * newApplicationsCount and temp toast * Added third dirt-activity-card component after the existing two cards * added newApplications to setAllAppsReportSummary * make button smaller * cleanup/nice-to-haves * remove comment * simplify activity card icon logic to use nullable iconClass * use buttonText presence to determine button display in activity card * apps needing review card - I think accidentally deleted when resolving merge conflicts * buttonClick.observed && buttonText --- apps/web/src/locales/en/messages.json | 18 ++++++++++++ .../services/all-activities.service.ts | 1 + .../services/risk-insights-report.service.ts | 20 +++++++++++-- .../activity-card.component.html | 19 +++++++++++- .../activity-card.component.ts | 29 +++++++++++++++++-- .../all-activity.component.html | 13 +++++++++ .../all-activity.component.ts | 19 ++++++++++++ 7 files changed, 114 insertions(+), 5 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index dafd7f00a04..cda4e70f915 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -280,6 +280,24 @@ "totalApplications": { "message": "Total applications" }, + "applicationsNeedingReview": { + "message": "Applications needing review" + }, + "newApplicationsWithCount": { + "message": "$COUNT$ new applications", + "placeholders": { + "count": { + "content": "$1", + "example": "13" + } + } + }, + "newApplicationsDescription": { + "message": "Review new applications to mark as critical and keep your organization secure" + }, + "reviewNow": { + "message": "Review now" + }, "unmarkAsCritical": { "message": "Unmark as critical" }, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts index b8992b1a05f..42e4b8975c4 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts @@ -68,6 +68,7 @@ export class AllActivitiesService { totalAtRiskMemberCount: summary.totalAtRiskMemberCount, totalApplicationCount: summary.totalApplicationCount, totalAtRiskApplicationCount: summary.totalAtRiskApplicationCount, + newApplications: summary.newApplications, }); } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index c9a51e804dc..dc078d810c2 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -226,7 +226,23 @@ export class RiskInsightsReportService { const atRiskMembers = reports.flatMap((x) => x.atRiskMemberDetails); const uniqueAtRiskMembers = getUniqueMembers(atRiskMembers); - // TODO: totalCriticalMemberCount, totalCriticalAtRiskMemberCount, totalCriticalApplicationCount, totalCriticalAtRiskApplicationCount, and newApplications will be handled with future logic implementation + // TODO: Replace with actual new applications detection logic (PM-26185) + const dummyNewApplications = [ + "github.com", + "google.com", + "stackoverflow.com", + "gitlab.com", + "bitbucket.org", + "npmjs.com", + "docker.com", + "aws.amazon.com", + "azure.microsoft.com", + "jenkins.io", + "terraform.io", + "kubernetes.io", + "atlassian.net", + ]; + return { totalMemberCount: uniqueMembers.length, totalAtRiskMemberCount: uniqueAtRiskMembers.length, @@ -236,7 +252,7 @@ export class RiskInsightsReportService { totalCriticalAtRiskMemberCount: 0, totalCriticalApplicationCount: 0, totalCriticalAtRiskApplicationCount: 0, - newApplications: [], + newApplications: dummyNewApplications, }; } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html index 17ae964dbed..0eb9b30367c 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html @@ -1,12 +1,29 @@ <div class="tw-flex-col"> <span bitTypography="h6" class="tw-flex tw-text-main">{{ title }}</span> <div class="tw-flex tw-items-baseline tw-gap-2"> + @if (iconClass) { + <i class="bwi {{ iconClass }} tw-text-muted" aria-hidden="true"></i> + } <span bitTypography="h3">{{ cardMetrics }}</span> </div> <div class="tw-flex tw-items-baseline tw-mt-4 tw-gap-2"> <span bitTypography="body2">{{ metricDescription }}</span> </div> - @if (showNavigationLink) { + @if (buttonClick.observed && buttonText) { + <div class="tw-flex tw-items-baseline tw-mt-4 tw-gap-2"> + <button + type="button" + bitButton + size="small" + [buttonType]="buttonType" + [attr.aria-label]="buttonText" + (click)="onButtonClick()" + > + {{ buttonText }} + </button> + </div> + } + @if (showNavigationLink && !buttonText) { <div class="tw-flex tw-items-baseline tw-mt-4 tw-gap-2"> <p bitTypography="body1"> <a bitLink (click)="navigateToLink(navigationLink)" rel="noreferrer"> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts index 2dc7c6a9c79..c8c73cd0e5a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts @@ -1,9 +1,9 @@ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ButtonModule, LinkModule, TypographyModule } from "@bitwarden/components"; +import { ButtonModule, ButtonType, LinkModule, TypographyModule } from "@bitwarden/components"; @Component({ selector: "dirt-activity-card", @@ -43,9 +43,34 @@ export class ActivityCardComponent { */ @Input() showNavigationLink: boolean = false; + /** + * Icon class to display next to metrics (e.g., "bwi-exclamation-triangle"). + * If null, no icon is displayed. + */ + @Input() iconClass: string | null = null; + + /** + * Button text. If provided, a button will be displayed instead of a navigation link. + */ + @Input() buttonText: string = ""; + + /** + * Button type (e.g., "primary", "secondary") + */ + @Input() buttonType: ButtonType = "primary"; + + /** + * Event emitted when button is clicked + */ + @Output() buttonClick = new EventEmitter<void>(); + constructor(private router: Router) {} navigateToLink = async (navigationLink: string) => { await this.router.navigateByUrl(navigationLink); }; + + onButtonClick = () => { + this.buttonClick.emit(); + }; } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index 390102aced9..844b2f92bb3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -41,5 +41,18 @@ > </dirt-activity-card> </li> + + <li class="tw-col-span-1"> + <dirt-activity-card + [title]="'applicationsNeedingReview' | i18n" + [cardMetrics]="'newApplicationsWithCount' | i18n: newApplicationsCount" + [metricDescription]="'newApplicationsDescription' | i18n" + [iconClass]="'bwi-exclamation-triangle'" + [buttonText]="'reviewNow' | i18n" + [buttonType]="'primary'" + (buttonClick)="onReviewNewApplications()" + > + </dirt-activity-card> + </li> </ul> } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts index 83ce743d6d0..31ab0351162 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts @@ -11,7 +11,9 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { getById } from "@bitwarden/common/platform/misc"; +import { ToastService } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { ActivityCardComponent } from "./activity-card.component"; @@ -34,6 +36,7 @@ export class AllActivityComponent implements OnInit { totalCriticalAppsAtRiskMemberCount = 0; totalCriticalAppsCount = 0; totalCriticalAppsAtRiskCount = 0; + newApplicationsCount = 0; passwordChangeMetricHasProgressBar = false; destroyRef = inject(DestroyRef); @@ -54,6 +57,7 @@ export class AllActivityComponent implements OnInit { this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; + this.newApplicationsCount = summary.newApplications.length; }); this.allActivitiesService.passwordChangeProgressMetricHasProgressBar$ @@ -70,6 +74,8 @@ export class AllActivityComponent implements OnInit { protected organizationService: OrganizationService, protected dataService: RiskInsightsDataService, protected allActivitiesService: AllActivitiesService, + private toastService: ToastService, + private i18nService: I18nService, ) {} get RiskInsightsTabType() { @@ -80,4 +86,17 @@ export class AllActivityComponent implements OnInit { const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); return `/organizations/${organizationId}/access-intelligence/risk-insights?tabIndex=${tabIndex}`; } + + /** + * Handles the review new applications button click. + * Shows a toast notification as a placeholder until the dialog is implemented. + * TODO: Implement dialog for reviewing new applications (follow-up task) + */ + onReviewNewApplications = () => { + this.toastService.showToast({ + variant: "info", + title: this.i18nService.t("applicationsNeedingReview"), + message: this.i18nService.t("newApplicationsWithCount", this.newApplicationsCount.toString()), + }); + }; } From 9aed9aa08ee81f0dbf77e55642f3542a31a544da Mon Sep 17 00:00:00 2001 From: Derek Nance <dnance@bitwarden.com> Date: Mon, 6 Oct 2025 15:31:06 -0500 Subject: [PATCH 74/83] [PM-25910] Refactor webpack config (#16616) This commit restructures the webpack configs for each project (i.e. web, browser, desktop, cli) such that each project has a base config that is shared in a way that requires less hard-coding of info, and more like simply calling a function with a few properties. --- apps/cli/webpack.base.js | 102 +++++ apps/cli/webpack.config.js | 94 +---- apps/desktop/package.json | 18 +- apps/desktop/webpack.base.js | 320 +++++++++++++++ apps/desktop/webpack.config.js | 18 + apps/desktop/webpack.main.js | 93 ----- apps/desktop/webpack.preload.js | 66 --- apps/desktop/webpack.renderer.js | 192 --------- apps/web/webpack.base.js | 431 ++++++++++++++++++++ apps/web/webpack.config.js | 415 +------------------ bitwarden_license/bit-cli/webpack.config.js | 17 +- bitwarden_license/bit-web/webpack.config.js | 18 +- 12 files changed, 907 insertions(+), 877 deletions(-) create mode 100644 apps/cli/webpack.base.js create mode 100644 apps/desktop/webpack.base.js create mode 100644 apps/desktop/webpack.config.js delete mode 100644 apps/desktop/webpack.main.js delete mode 100644 apps/desktop/webpack.preload.js delete mode 100644 apps/desktop/webpack.renderer.js create mode 100644 apps/web/webpack.base.js diff --git a/apps/cli/webpack.base.js b/apps/cli/webpack.base.js new file mode 100644 index 00000000000..b937a4b9641 --- /dev/null +++ b/apps/cli/webpack.base.js @@ -0,0 +1,102 @@ +const path = require("path"); +const webpack = require("webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const nodeExternals = require("webpack-node-externals"); +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); +const config = require("./config/config"); + +module.exports.getEnv = function getEnv() { + const ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; + return { ENV }; +}; + +/** + * + * @param {{ + * configName: string; + * entry: string; + * tsConfig: string; + * }} params + */ +module.exports.buildConfig = function buildConfig(params) { + const { ENV } = module.exports.getEnv(); + + const envConfig = config.load(ENV); + config.log(`Building CLI - ${params.configName} version`); + config.log(envConfig); + + const moduleRules = [ + { + test: /\.ts$/, + use: "ts-loader", + exclude: path.resolve(__dirname, "node_modules"), + }, + ]; + + const plugins = [ + new CopyWebpackPlugin({ + patterns: [{ from: "./src/locales", to: "locales" }], + }), + new webpack.DefinePlugin({ + "process.env.BWCLI_ENV": JSON.stringify(ENV), + }), + new webpack.BannerPlugin({ + banner: "#!/usr/bin/env node", + raw: true, + }), + new webpack.IgnorePlugin({ + resourceRegExp: /^encoding$/, + contextRegExp: /node-fetch/, + }), + new webpack.EnvironmentPlugin({ + ENV: ENV, + BWCLI_ENV: ENV, + FLAGS: envConfig.flags, + DEV_FLAGS: envConfig.devFlags, + }), + new webpack.IgnorePlugin({ + resourceRegExp: /canvas/, + contextRegExp: /jsdom$/, + }), + ]; + + const webpackConfig = { + mode: ENV, + target: "node", + devtool: ENV === "development" ? "eval-source-map" : "source-map", + node: { + __dirname: false, + __filename: false, + }, + entry: { + bw: params.entry, + }, + optimization: { + minimize: false, + }, + resolve: { + extensions: [".ts", ".js"], + symlinks: false, + modules: [path.resolve("../../node_modules")], + plugins: [new TsconfigPathsPlugin({ configFile: params.tsConfig })], + }, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "build"), + clean: true, + }, + module: { rules: moduleRules }, + plugins: plugins, + externals: [ + nodeExternals({ + modulesDir: "../../node_modules", + allowlist: [/@bitwarden/], + }), + ], + experiments: { + asyncWebAssembly: true, + }, + }; + + return webpackConfig; +}; diff --git a/apps/cli/webpack.config.js b/apps/cli/webpack.config.js index d5f66af73ec..7ac290402cc 100644 --- a/apps/cli/webpack.config.js +++ b/apps/cli/webpack.config.js @@ -1,89 +1,7 @@ -const path = require("path"); -const webpack = require("webpack"); -const CopyWebpackPlugin = require("copy-webpack-plugin"); -const nodeExternals = require("webpack-node-externals"); -const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); -const config = require("./config/config"); +const { buildConfig } = require("./webpack.base"); -if (process.env.NODE_ENV == null) { - process.env.NODE_ENV = "development"; -} -const ENV = (process.env.ENV = process.env.NODE_ENV); - -const envConfig = config.load(ENV); -config.log(envConfig); - -const moduleRules = [ - { - test: /\.ts$/, - use: "ts-loader", - exclude: path.resolve(__dirname, "node_modules"), - }, -]; - -const plugins = [ - new CopyWebpackPlugin({ - patterns: [{ from: "./src/locales", to: "locales" }], - }), - new webpack.DefinePlugin({ - "process.env.BWCLI_ENV": JSON.stringify(ENV), - }), - new webpack.BannerPlugin({ - banner: "#!/usr/bin/env node", - raw: true, - }), - new webpack.IgnorePlugin({ - resourceRegExp: /^encoding$/, - contextRegExp: /node-fetch/, - }), - new webpack.EnvironmentPlugin({ - ENV: ENV, - BWCLI_ENV: ENV, - FLAGS: envConfig.flags, - DEV_FLAGS: envConfig.devFlags, - }), - new webpack.IgnorePlugin({ - resourceRegExp: /canvas/, - contextRegExp: /jsdom$/, - }), -]; - -const webpackConfig = { - mode: ENV, - target: "node", - devtool: ENV === "development" ? "eval-source-map" : "source-map", - node: { - __dirname: false, - __filename: false, - }, - entry: { - bw: "./src/bw.ts", - }, - optimization: { - minimize: false, - }, - resolve: { - extensions: [".ts", ".js"], - symlinks: false, - modules: [path.resolve("../../node_modules")], - plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })], - }, - output: { - filename: "[name].js", - path: path.resolve(__dirname, "build"), - clean: true, - }, - module: { rules: moduleRules }, - plugins: plugins, - externals: [ - nodeExternals({ - modulesDir: "../../node_modules", - allowlist: [/@bitwarden/], - }), - ], - experiments: { - asyncWebAssembly: true, - }, -}; - -module.exports = webpackConfig; +module.exports = buildConfig({ + configName: "OSS", + entry: "./src/bw.ts", + tsConfig: "./tsconfig.json", +}); diff --git a/apps/desktop/package.json b/apps/desktop/package.json index cb997273f1e..ce4c91f0d8c 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -21,18 +21,18 @@ "build-native": "cd desktop_native && node build.js", "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\" \"npm run build:preload:dev\"", - "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", - "build:preload:dev": "cross-env NODE_ENV=development webpack --config webpack.preload.js", - "build:preload:watch": "cross-env NODE_ENV=development webpack --config webpack.preload.js --watch", + "build:preload": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name preload", + "build:preload:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name preload", + "build:preload:watch": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name preload --watch", "build:macos-extension:mac": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mac", "build:macos-extension:mas": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas", "build:macos-extension:masdev": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas-dev", - "build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js", - "build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js", - "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js --watch", - "build:renderer": "cross-env NODE_ENV=production webpack --config webpack.renderer.js", - "build:renderer:dev": "cross-env NODE_ENV=development webpack --config webpack.renderer.js", - "build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.renderer.js --watch", + "build:main": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name main", + "build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.config.js --config-name main", + "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.config.js --config-name main --watch", + "build:renderer": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name renderer", + "build:renderer:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name renderer", + "build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name renderer --watch", "electron": "node ./scripts/start.js", "electron:ignore": "node ./scripts/start.js --ignore-certificate-errors", "clean:dist": "rimraf ./dist", diff --git a/apps/desktop/webpack.base.js b/apps/desktop/webpack.base.js new file mode 100644 index 00000000000..fe3079b730f --- /dev/null +++ b/apps/desktop/webpack.base.js @@ -0,0 +1,320 @@ +const path = require("path"); +const webpack = require("webpack"); +const { merge } = require("webpack-merge"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const { AngularWebpackPlugin } = require("@ngtools/webpack"); +const TerserPlugin = require("terser-webpack-plugin"); +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); +const { EnvironmentPlugin, DefinePlugin } = require("webpack"); +const configurator = require("./config/config"); + +module.exports.getEnv = function getEnv() { + const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; + const ENV = process.env.ENV == null ? "development" : process.env.ENV; + + return { NODE_ENV, ENV }; +}; + +/** + * @param {{ + * configName: string; + * renderer: { + * entry: string; + * entryModule: string; + * tsConfig: string; + * }; + * main: { + * entry: string; + * tsConfig: string; + * }; + * preload: { + * entry: string; + * tsConfig: string; + * }; + * }} params + */ +module.exports.buildConfig = function buildConfig(params) { + const { NODE_ENV, ENV } = module.exports.getEnv(); + + console.log(`Building ${params.configName} Desktop App`); + + const envConfig = configurator.load(NODE_ENV); + configurator.log(envConfig); + + const commonConfig = { + resolve: { + extensions: [".tsx", ".ts", ".js"], + symlinks: false, + modules: [path.resolve("../../node_modules")], + }, + }; + + const getOutputConfig = (isDev) => ({ + filename: "[name].js", + path: path.resolve(__dirname, "build"), + ...(isDev && { devtoolModuleFilenameTemplate: "[absolute-resource-path]" }), + }); + + const mainConfig = { + name: "main", + mode: NODE_ENV, + target: "electron-main", + node: { + __dirname: false, + __filename: false, + }, + entry: { + main: params.main.entry, + }, + optimization: { + minimize: false, + }, + output: getOutputConfig(NODE_ENV === "development"), + devtool: NODE_ENV === "development" ? "cheap-source-map" : false, + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules\/(?!(@bitwarden)\/).*/, + }, + { + test: /\.node$/, + loader: "node-loader", + }, + ], + }, + experiments: { + asyncWebAssembly: true, + }, + resolve: { + ...commonConfig.resolve, + plugins: [new TsconfigPathsPlugin({ configFile: params.main.tsConfig })], + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + "./src/package.json", + { from: "./src/images", to: "images" }, + { from: "./src/locales", to: "locales" }, + ], + }), + new DefinePlugin({ + BIT_ENVIRONMENT: JSON.stringify(NODE_ENV), + }), + new EnvironmentPlugin({ + FLAGS: envConfig.flags, + DEV_FLAGS: NODE_ENV === "development" ? envConfig.devFlags : {}, + }), + ], + externals: { + "electron-reload": "commonjs2 electron-reload", + "@bitwarden/desktop-napi": "commonjs2 @bitwarden/desktop-napi", + }, + }; + + const preloadConfig = { + name: "preload", + mode: NODE_ENV, + target: "electron-preload", + node: { + __dirname: false, + __filename: false, + }, + entry: { + preload: params.preload.entry, + }, + optimization: { + minimize: false, + }, + output: getOutputConfig(NODE_ENV === "development"), + devtool: NODE_ENV === "development" ? "cheap-source-map" : false, + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules\/(?!(@bitwarden)\/).*/, + }, + ], + }, + resolve: { + ...commonConfig.resolve, + plugins: [new TsconfigPathsPlugin({ configFile: params.preload.tsConfig })], + }, + plugins: [ + new DefinePlugin({ + BIT_ENVIRONMENT: JSON.stringify(NODE_ENV), + }), + ], + }; + + const rendererConfig = { + name: "renderer", + mode: NODE_ENV, + devtool: "source-map", + target: "web", + node: { + __dirname: false, + }, + entry: { + "app/main": params.renderer.entry, + }, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "build"), + }, + optimization: { + minimizer: [ + new TerserPlugin({ + terserOptions: { + // Replicate Angular CLI behaviour + compress: { + global_defs: { + ngDevMode: false, + ngI18nClosureMode: false, + }, + }, + }, + }), + ], + splitChunks: { + cacheGroups: { + commons: { + test: /[\\/]node_modules[\\/]/, + name: "app/vendor", + chunks: (chunk) => { + return chunk.name === "app/main"; + }, + }, + }, + }, + }, + module: { + rules: [ + { + test: /\.[cm]?js$/, + use: [ + { + loader: "babel-loader", + options: { + configFile: "../../babel.config.json", + }, + }, + ], + }, + { + test: /\.[jt]sx?$/, + loader: "@ngtools/webpack", + }, + { + test: /\.(html)$/, + loader: "html-loader", + }, + { + test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, + exclude: /loading.svg/, + generator: { + filename: "fonts/[name].[contenthash][ext]", + }, + type: "asset/resource", + }, + { + test: /\.(jpe?g|png|gif|svg)$/i, + exclude: /.*(bwi-font)\.svg/, + generator: { + filename: "images/[name][ext]", + }, + type: "asset/resource", + }, + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + }, + "css-loader", + "resolve-url-loader", + { + loader: "postcss-loader", + options: { + sourceMap: true, + }, + }, + ], + }, + { + test: /\.scss$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: { + publicPath: "../", + }, + }, + "css-loader", + "resolve-url-loader", + { + loader: "sass-loader", + options: { + sourceMap: true, + }, + }, + ], + }, + // Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560 + { + test: /[\/\\]@angular[\/\\].+\.js$/, + parser: { system: true }, + }, + ], + }, + experiments: { + asyncWebAssembly: true, + }, + resolve: { + ...commonConfig.resolve, + fallback: { + path: require.resolve("path-browserify"), + fs: false, + }, + }, + plugins: [ + new AngularWebpackPlugin({ + tsConfigPath: params.renderer.tsConfig, + entryModule: params.renderer.entryModule, + sourceMap: true, + }), + // ref: https://github.com/angular/angular/issues/20357 + new webpack.ContextReplacementPlugin( + /\@angular(\\|\/)core(\\|\/)fesm5/, + path.resolve(__dirname, "./src"), + ), + new HtmlWebpackPlugin({ + template: "./src/index.html", + filename: "index.html", + chunks: ["app/vendor", "app/main"], + }), + new webpack.SourceMapDevToolPlugin({ + include: ["app/main.js"], + }), + new MiniCssExtractPlugin({ + filename: "[name].[contenthash].css", + chunkFilename: "[id].[contenthash].css", + }), + new webpack.DefinePlugin({ + BIT_ENVIRONMENT: JSON.stringify(NODE_ENV), + }), + new webpack.EnvironmentPlugin({ + ENV: ENV, + FLAGS: envConfig.flags, + DEV_FLAGS: NODE_ENV === "development" ? envConfig.devFlags : {}, + ADDITIONAL_REGIONS: envConfig.additionalRegions ?? [], + }), + ], + }; + + return [mainConfig, rendererConfig, preloadConfig]; +}; diff --git a/apps/desktop/webpack.config.js b/apps/desktop/webpack.config.js new file mode 100644 index 00000000000..5ba0df337ee --- /dev/null +++ b/apps/desktop/webpack.config.js @@ -0,0 +1,18 @@ +const { buildConfig } = require("./webpack.base"); + +module.exports = buildConfig({ + configName: "OSS", + renderer: { + entry: "./src/app/main.ts", + entryModule: "src/app/app.module#AppModule", + tsConfig: "./tsconfig.renderer.json", + }, + main: { + entry: "./src/entry.ts", + tsConfig: "./tsconfig.json", + }, + preload: { + entry: "./src/preload.ts", + tsConfig: "./tsconfig.json", + }, +}); diff --git a/apps/desktop/webpack.main.js b/apps/desktop/webpack.main.js deleted file mode 100644 index 151b1d0cea2..00000000000 --- a/apps/desktop/webpack.main.js +++ /dev/null @@ -1,93 +0,0 @@ -const path = require("path"); -const { merge } = require("webpack-merge"); -const CopyWebpackPlugin = require("copy-webpack-plugin"); -const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); -const configurator = require("./config/config"); -const { EnvironmentPlugin, DefinePlugin } = require("webpack"); - -const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; - -console.log("Main process config"); -const envConfig = configurator.load(NODE_ENV); -configurator.log(envConfig); - -const common = { - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules\/(?!(@bitwarden)\/).*/, - }, - ], - }, - plugins: [], - resolve: { - extensions: [".tsx", ".ts", ".js"], - plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })], - }, -}; - -const prod = { - output: { - filename: "[name].js", - path: path.resolve(__dirname, "build"), - }, -}; - -const dev = { - output: { - filename: "[name].js", - path: path.resolve(__dirname, "build"), - devtoolModuleFilenameTemplate: "[absolute-resource-path]", - }, - devtool: "cheap-source-map", -}; - -const main = { - mode: NODE_ENV, - target: "electron-main", - node: { - __dirname: false, - __filename: false, - }, - entry: { - main: "./src/entry.ts", - }, - optimization: { - minimize: false, - }, - module: { - rules: [ - { - test: /\.node$/, - loader: "node-loader", - }, - ], - }, - experiments: { - asyncWebAssembly: true, - }, - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - "./src/package.json", - { from: "./src/images", to: "images" }, - { from: "./src/locales", to: "locales" }, - ], - }), - new DefinePlugin({ - BIT_ENVIRONMENT: JSON.stringify(NODE_ENV), - }), - new EnvironmentPlugin({ - FLAGS: envConfig.flags, - DEV_FLAGS: NODE_ENV === "development" ? envConfig.devFlags : {}, - }), - ], - externals: { - "electron-reload": "commonjs2 electron-reload", - "@bitwarden/desktop-napi": "commonjs2 @bitwarden/desktop-napi", - }, -}; - -module.exports = merge(common, NODE_ENV === "development" ? dev : prod, main); diff --git a/apps/desktop/webpack.preload.js b/apps/desktop/webpack.preload.js deleted file mode 100644 index db75e882644..00000000000 --- a/apps/desktop/webpack.preload.js +++ /dev/null @@ -1,66 +0,0 @@ -const path = require("path"); -const { merge } = require("webpack-merge"); -const CopyWebpackPlugin = require("copy-webpack-plugin"); -const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); -const configurator = require("./config/config"); -const { EnvironmentPlugin, DefinePlugin } = require("webpack"); - -const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; - -console.log("Preload process config"); -const envConfig = configurator.load(NODE_ENV); -configurator.log(envConfig); - -const common = { - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules\/(?!(@bitwarden)\/).*/, - }, - ], - }, - plugins: [ - new DefinePlugin({ - BIT_ENVIRONMENT: JSON.stringify(NODE_ENV), - }), - ], - resolve: { - extensions: [".tsx", ".ts", ".js"], - plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })], - }, -}; - -const prod = { - output: { - filename: "[name].js", - path: path.resolve(__dirname, "build"), - }, -}; - -const dev = { - output: { - filename: "[name].js", - path: path.resolve(__dirname, "build"), - devtoolModuleFilenameTemplate: "[absolute-resource-path]", - }, - devtool: "cheap-source-map", -}; - -const main = { - mode: NODE_ENV, - target: "electron-preload", - node: { - __dirname: false, - __filename: false, - }, - entry: { - preload: "./src/preload.ts", - }, - optimization: { - minimize: false, - }, -}; - -module.exports = merge(common, NODE_ENV === "development" ? dev : prod, main); diff --git a/apps/desktop/webpack.renderer.js b/apps/desktop/webpack.renderer.js deleted file mode 100644 index 9c5b0fd2584..00000000000 --- a/apps/desktop/webpack.renderer.js +++ /dev/null @@ -1,192 +0,0 @@ -const path = require("path"); -const webpack = require("webpack"); -const { merge } = require("webpack-merge"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); -const { AngularWebpackPlugin } = require("@ngtools/webpack"); -const TerserPlugin = require("terser-webpack-plugin"); -const configurator = require("./config/config"); - -const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; - -console.log("Renderer process config"); -const envConfig = configurator.load(NODE_ENV); -configurator.log(envConfig); - -const ENV = process.env.ENV == null ? "development" : process.env.ENV; - -const common = { - module: { - rules: [ - { - test: /\.[cm]?js$/, - use: [ - { - loader: "babel-loader", - options: { - configFile: "../../babel.config.json", - }, - }, - ], - }, - { - test: /\.[jt]sx?$/, - loader: "@ngtools/webpack", - }, - { - test: /\.(jpe?g|png|gif|svg)$/i, - exclude: /.*(bwi-font)\.svg/, - generator: { - filename: "images/[name][ext]", - }, - type: "asset/resource", - }, - ], - }, - plugins: [], - resolve: { - extensions: [".tsx", ".ts", ".js"], - symlinks: false, - modules: [path.resolve("../../node_modules")], - fallback: { - path: require.resolve("path-browserify"), - fs: false, - }, - }, - output: { - filename: "[name].js", - path: path.resolve(__dirname, "build"), - }, -}; - -const renderer = { - mode: NODE_ENV, - devtool: "source-map", - target: "web", - node: { - __dirname: false, - }, - entry: { - "app/main": "./src/app/main.ts", - }, - optimization: { - minimizer: [ - new TerserPlugin({ - terserOptions: { - // Replicate Angular CLI behaviour - compress: { - global_defs: { - ngDevMode: false, - ngI18nClosureMode: false, - }, - }, - }, - }), - ], - splitChunks: { - cacheGroups: { - commons: { - test: /[\\/]node_modules[\\/]/, - name: "app/vendor", - chunks: (chunk) => { - return chunk.name === "app/main"; - }, - }, - }, - }, - }, - module: { - rules: [ - { - test: /\.(html)$/, - loader: "html-loader", - }, - { - test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, - exclude: /loading.svg/, - generator: { - filename: "fonts/[name].[contenthash][ext]", - }, - type: "asset/resource", - }, - { - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - }, - "css-loader", - "resolve-url-loader", - { - loader: "postcss-loader", - options: { - sourceMap: true, - }, - }, - ], - }, - { - test: /\.scss$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: { - publicPath: "../", - }, - }, - "css-loader", - "resolve-url-loader", - { - loader: "sass-loader", - options: { - sourceMap: true, - }, - }, - ], - }, - // Hide System.import warnings. ref: https://github.com/angular/angular/issues/21560 - { - test: /[\/\\]@angular[\/\\].+\.js$/, - parser: { system: true }, - }, - ], - }, - experiments: { - asyncWebAssembly: true, - }, - plugins: [ - new AngularWebpackPlugin({ - tsConfigPath: "tsconfig.renderer.json", - entryModule: "src/app/app.module#AppModule", - sourceMap: true, - }), - // ref: https://github.com/angular/angular/issues/20357 - new webpack.ContextReplacementPlugin( - /\@angular(\\|\/)core(\\|\/)fesm5/, - path.resolve(__dirname, "./src"), - ), - new HtmlWebpackPlugin({ - template: "./src/index.html", - filename: "index.html", - chunks: ["app/vendor", "app/main"], - }), - new webpack.SourceMapDevToolPlugin({ - include: ["app/main.js"], - }), - new MiniCssExtractPlugin({ - filename: "[name].[contenthash].css", - chunkFilename: "[id].[contenthash].css", - }), - new webpack.DefinePlugin({ - BIT_ENVIRONMENT: JSON.stringify(NODE_ENV), - }), - new webpack.EnvironmentPlugin({ - ENV: ENV, - FLAGS: envConfig.flags, - DEV_FLAGS: NODE_ENV === "development" ? envConfig.devFlags : {}, - ADDITIONAL_REGIONS: envConfig.additionalRegions ?? [], - }), - ], -}; - -module.exports = merge(common, renderer); diff --git a/apps/web/webpack.base.js b/apps/web/webpack.base.js new file mode 100644 index 00000000000..2bfe0e27553 --- /dev/null +++ b/apps/web/webpack.base.js @@ -0,0 +1,431 @@ +const fs = require("fs"); +const path = require("path"); + +const { AngularWebpackPlugin } = require("@ngtools/webpack"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const HtmlWebpackInjector = require("html-webpack-injector"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const TerserPlugin = require("terser-webpack-plugin"); +const webpack = require("webpack"); + +const config = require("./config.js"); +const pjson = require("./package.json"); + +module.exports.getEnv = function getEnv() { + const ENV = process.env.ENV == null ? "development" : process.env.ENV; + const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; + const LOGGING = process.env.LOGGING != "false"; + + return { ENV, NODE_ENV, LOGGING }; +}; + +/** + * + * @param {{ + * configName: string; + * app: { + * entry: string; + * entryModule: string; + * }; + * tsConfig: string; + * }} params + */ +module.exports.buildConfig = function buildConfig(params) { + const { ENV, NODE_ENV, LOGGING } = module.exports.getEnv(); + + const envConfig = config.load(ENV); + if (LOGGING) { + config.log(`Building web - ${params.configName} version`); + config.log(envConfig); + } + + const moduleRules = [ + { + test: /\.(html)$/, + loader: "html-loader", + }, + { + test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, + exclude: /loading(|-white).svg/, + generator: { + filename: "fonts/[name].[contenthash][ext]", + }, + type: "asset/resource", + }, + { + test: /\.(jpe?g|png|gif|svg|webp|avif)$/i, + exclude: /.*(bwi-font)\.svg/, + generator: { + filename: "images/[name][ext]", + }, + type: "asset/resource", + }, + { + test: /\.scss$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + }, + "css-loader", + "resolve-url-loader", + { + loader: "sass-loader", + options: { + sourceMap: true, + }, + }, + ], + }, + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + }, + "css-loader", + "resolve-url-loader", + { + loader: "postcss-loader", + options: { + sourceMap: true, + }, + }, + ], + }, + { + test: /\.[cm]?js$/, + use: [ + { + loader: "babel-loader", + options: { + configFile: "../../babel.config.json", + cacheDirectory: NODE_ENV !== "production", + }, + }, + ], + }, + { + test: /\.[jt]sx?$/, + loader: "@ngtools/webpack", + }, + ]; + + const plugins = [ + new HtmlWebpackPlugin({ + template: "./src/index.html", + filename: "index.html", + chunks: ["theme_head", "app/polyfills", "app/vendor", "app/main", "styles"], + }), + new HtmlWebpackInjector(), + new HtmlWebpackPlugin({ + template: "./src/connectors/webauthn.html", + filename: "webauthn-connector.html", + chunks: ["connectors/webauthn", "styles"], + }), + new HtmlWebpackPlugin({ + template: "./src/connectors/webauthn-mobile.html", + filename: "webauthn-mobile-connector.html", + chunks: ["connectors/webauthn", "styles"], + }), + new HtmlWebpackPlugin({ + template: "./src/connectors/webauthn-fallback.html", + filename: "webauthn-fallback-connector.html", + chunks: ["connectors/webauthn-fallback", "styles"], + }), + new HtmlWebpackPlugin({ + template: "./src/connectors/sso.html", + filename: "sso-connector.html", + chunks: ["connectors/sso", "styles"], + }), + new HtmlWebpackPlugin({ + template: "./src/connectors/redirect.html", + filename: "redirect-connector.html", + chunks: ["connectors/redirect", "styles"], + }), + new HtmlWebpackPlugin({ + template: "./src/connectors/duo-redirect.html", + filename: "duo-redirect-connector.html", + chunks: ["connectors/duo-redirect", "styles"], + }), + new HtmlWebpackPlugin({ + template: "./src/404.html", + filename: "404.html", + chunks: ["styles"], + // 404 page is a wildcard, this ensures it uses absolute paths. + publicPath: "/", + }), + new CopyWebpackPlugin({ + patterns: [ + { from: "./src/.nojekyll" }, + { from: "./src/manifest.json" }, + { from: "./src/favicon.ico" }, + { from: "./src/browserconfig.xml" }, + { from: "./src/app-id.json" }, + { from: "./src/images", to: "images" }, + { from: "./src/videos", to: "videos" }, + { from: "./src/locales", to: "locales" }, + { from: "../../node_modules/qrious/dist/qrious.min.js", to: "scripts" }, + { from: "../../node_modules/braintree-web-drop-in/dist/browser/dropin.js", to: "scripts" }, + { + from: "./src/version.json", + transform(content, path) { + return content.toString().replace("process.env.APPLICATION_VERSION", pjson.version); + }, + }, + ], + }), + new MiniCssExtractPlugin({ + filename: "[name].[contenthash].css", + chunkFilename: "[id].[contenthash].css", + }), + new webpack.ProvidePlugin({ + process: "process/browser.js", + }), + new webpack.EnvironmentPlugin({ + ENV: ENV, + NODE_ENV: NODE_ENV === "production" ? "production" : "development", + APPLICATION_VERSION: pjson.version, + CACHE_TAG: Math.random().toString(36).substring(7), + URLS: envConfig["urls"] ?? {}, + STRIPE_KEY: envConfig["stripeKey"] ?? "", + BRAINTREE_KEY: envConfig["braintreeKey"] ?? "", + PAYPAL_CONFIG: envConfig["paypal"] ?? {}, + FLAGS: envConfig["flags"] ?? {}, + DEV_FLAGS: NODE_ENV === "development" ? envConfig["devFlags"] : {}, + ADDITIONAL_REGIONS: envConfig["additionalRegions"] ?? [], + }), + new AngularWebpackPlugin({ + tsconfig: params.tsConfig, + entryModule: params.app.entryModule, + sourceMap: true, + }), + ]; + + // ref: https://webpack.js.org/configuration/dev-server/#devserver + let certSuffix = fs.existsSync("dev-server.local.pem") ? ".local" : ".shared"; + const devServer = + NODE_ENV !== "development" + ? {} + : { + server: { + type: "https", + options: { + key: fs.readFileSync("dev-server" + certSuffix + ".pem"), + cert: fs.readFileSync("dev-server" + certSuffix + ".pem"), + }, + }, + // host: '192.168.1.9', + proxy: [ + { + context: ["/api"], + target: envConfig.dev?.proxyApi, + pathRewrite: { "^/api": "" }, + secure: false, + changeOrigin: true, + }, + { + context: ["/identity"], + target: envConfig.dev?.proxyIdentity, + pathRewrite: { "^/identity": "" }, + secure: false, + changeOrigin: true, + }, + { + context: ["/events"], + target: envConfig.dev?.proxyEvents, + pathRewrite: { "^/events": "" }, + secure: false, + changeOrigin: true, + }, + { + context: ["/notifications"], + target: envConfig.dev?.proxyNotifications, + pathRewrite: { "^/notifications": "" }, + secure: false, + changeOrigin: true, + ws: true, + }, + { + context: ["/icons"], + target: envConfig.dev?.proxyIcons, + pathRewrite: { "^/icons": "" }, + secure: false, + changeOrigin: true, + }, + ], + headers: (req) => { + if (!req.originalUrl.includes("connector.html")) { + return { + "Content-Security-Policy": ` + default-src 'self' + ;script-src + 'self' + 'wasm-unsafe-eval' + 'sha256-ryoU+5+IUZTuUyTElqkrQGBJXr1brEv6r2CA62WUw8w=' + https://js.stripe.com + https://js.braintreegateway.com + https://www.paypalobjects.com + ;style-src + 'self' + https://assets.braintreegateway.com + https://*.paypal.com + ${"'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='" /* date input polyfill */} + ${"'sha256-JVRXyYPueLWdwGwY9m/7u4QlZ1xeQdqUj2t8OVIzZE4='" /* date input polyfill */} + ${"'sha256-EnIJNDxVnh0++RytXJOkU0sqtLDFt1nYUDOfeJ5SKxg='" /* ng-select */} + ${"'sha256-dbBsIsz2pJ5loaLjhE6xWlmhYdjl6ghbwnGSCr4YObs='" /* cdk-virtual-scroll */} + ${"'sha256-S+uMh1G1SNQDAMG3seBmknQ26Wh+KSEoKdsNiy0joEE='" /* cdk-visually-hidden */} + ;img-src + 'self' + data: + https://icons.bitwarden.net + https://*.paypal.com + https://www.paypalobjects.com + https://q.stripe.com + https://haveibeenpwned.com + ;media-src + 'self' + https://assets.bitwarden.com + ;child-src + 'self' + https://js.stripe.com + https://assets.braintreegateway.com + https://*.paypal.com + https://*.duosecurity.com + ;frame-src + 'self' + https://js.stripe.com + https://assets.braintreegateway.com + https://*.paypal.com + https://*.duosecurity.com + ;connect-src + 'self' + ${envConfig.dev.wsConnectSrc ?? ""} + wss://notifications.bitwarden.com + https://notifications.bitwarden.com + https://cdn.bitwarden.net + https://api.pwnedpasswords.com + https://api.2fa.directory/v3/totp.json + https://api.stripe.com + https://www.paypal.com + https://api.sandbox.braintreegateway.com + https://api.braintreegateway.com + https://client-analytics.braintreegateway.com + https://*.braintree-api.com + https://*.blob.core.windows.net + http://127.0.0.1:10000 + https://app.simplelogin.io/api/alias/random/new + https://quack.duckduckgo.com/api/email/addresses + https://app.addy.io/api/v1/aliases + https://api.fastmail.com + https://api.forwardemail.net + http://localhost:5000 + ;object-src + 'self' + blob: + ;` + .replace(/\n/g, " ") + .replace(/ +(?= )/g, ""), + }; + } + }, + hot: false, + port: envConfig.dev?.port ?? 8080, + allowedHosts: envConfig.dev?.allowedHosts ?? "auto", + client: { + overlay: { + errors: true, + warnings: false, + runtimeErrors: false, + }, + }, + }; + + const webpackConfig = { + mode: NODE_ENV, + devtool: "source-map", + devServer: devServer, + target: "web", + entry: { + "app/polyfills": "./src/polyfills.ts", + "app/main": params.app.entry, + "connectors/webauthn": "./src/connectors/webauthn.ts", + "connectors/webauthn-fallback": "./src/connectors/webauthn-fallback.ts", + "connectors/sso": "./src/connectors/sso.ts", + "connectors/duo-redirect": "./src/connectors/duo-redirect.ts", + "connectors/redirect": "./src/connectors/redirect.ts", + styles: ["./src/scss/styles.scss", "./src/scss/tailwind.css"], + theme_head: "./src/theme.ts", + }, + cache: + NODE_ENV === "production" + ? false + : { + type: "filesystem", + allowCollectingMemory: true, + cacheDirectory: path.resolve(__dirname, "../../node_modules/.cache/webpack"), + buildDependencies: { + config: [__filename], + }, + }, + snapshot: { + unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")], + }, + optimization: { + splitChunks: { + cacheGroups: { + commons: { + test: /[\\/]node_modules[\\/]/, + name: "app/vendor", + chunks: (chunk) => { + return chunk.name === "app/main"; + }, + }, + }, + }, + minimize: NODE_ENV === "production", + minimizer: [ + new TerserPlugin({ + terserOptions: { + safari10: true, + // Replicate Angular CLI behaviour + compress: { + global_defs: { + ngDevMode: false, + ngI18nClosureMode: false, + }, + }, + }, + }), + ], + }, + resolve: { + extensions: [".ts", ".js"], + symlinks: false, + modules: [path.resolve("../../node_modules")], + fallback: { + buffer: false, + util: require.resolve("util/"), + assert: false, + url: false, + fs: false, + process: false, + path: require.resolve("path-browserify"), + }, + }, + output: { + filename: "[name].[contenthash].js", + path: path.resolve(__dirname, "build"), + clean: true, + }, + module: { + rules: moduleRules, + }, + experiments: { + asyncWebAssembly: true, + }, + plugins: plugins, + }; + + return webpackConfig; +}; diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index b311499dd55..e9d7bd46002 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -1,411 +1,10 @@ -const fs = require("fs"); -const path = require("path"); +const { buildConfig } = require("./webpack.base"); -const { AngularWebpackPlugin } = require("@ngtools/webpack"); -const CopyWebpackPlugin = require("copy-webpack-plugin"); -const HtmlWebpackInjector = require("html-webpack-injector"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); -const TerserPlugin = require("terser-webpack-plugin"); -const webpack = require("webpack"); - -const config = require("./config.js"); -const pjson = require("./package.json"); - -const ENV = process.env.ENV == null ? "development" : process.env.ENV; -const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; -const LOGGING = process.env.LOGGING != "false"; - -const envConfig = config.load(ENV); -if (LOGGING) { - config.log(envConfig); -} - -const moduleRules = [ - { - test: /\.(html)$/, - loader: "html-loader", - }, - { - test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, - exclude: /loading(|-white).svg/, - generator: { - filename: "fonts/[name].[contenthash][ext]", - }, - type: "asset/resource", - }, - { - test: /\.(jpe?g|png|gif|svg|webp|avif)$/i, - exclude: /.*(bwi-font)\.svg/, - generator: { - filename: "images/[name][ext]", - }, - type: "asset/resource", - }, - { - test: /\.scss$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - }, - "css-loader", - "resolve-url-loader", - { - loader: "sass-loader", - options: { - sourceMap: true, - }, - }, - ], - }, - { - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - }, - "css-loader", - "resolve-url-loader", - { - loader: "postcss-loader", - options: { - sourceMap: true, - }, - }, - ], - }, - { - test: /\.[cm]?js$/, - use: [ - { - loader: "babel-loader", - options: { - configFile: "../../babel.config.json", - cacheDirectory: NODE_ENV !== "production", - }, - }, - ], - }, - { - test: /\.[jt]sx?$/, - loader: "@ngtools/webpack", - }, -]; - -const plugins = [ - new HtmlWebpackPlugin({ - template: "./src/index.html", - filename: "index.html", - chunks: ["theme_head", "app/polyfills", "app/vendor", "app/main", "styles"], - }), - new HtmlWebpackInjector(), - new HtmlWebpackPlugin({ - template: "./src/connectors/webauthn.html", - filename: "webauthn-connector.html", - chunks: ["connectors/webauthn", "styles"], - }), - new HtmlWebpackPlugin({ - template: "./src/connectors/webauthn-mobile.html", - filename: "webauthn-mobile-connector.html", - chunks: ["connectors/webauthn", "styles"], - }), - new HtmlWebpackPlugin({ - template: "./src/connectors/webauthn-fallback.html", - filename: "webauthn-fallback-connector.html", - chunks: ["connectors/webauthn-fallback", "styles"], - }), - new HtmlWebpackPlugin({ - template: "./src/connectors/sso.html", - filename: "sso-connector.html", - chunks: ["connectors/sso", "styles"], - }), - new HtmlWebpackPlugin({ - template: "./src/connectors/redirect.html", - filename: "redirect-connector.html", - chunks: ["connectors/redirect", "styles"], - }), - new HtmlWebpackPlugin({ - template: "./src/connectors/duo-redirect.html", - filename: "duo-redirect-connector.html", - chunks: ["connectors/duo-redirect", "styles"], - }), - new HtmlWebpackPlugin({ - template: "./src/404.html", - filename: "404.html", - chunks: ["styles"], - // 404 page is a wildcard, this ensures it uses absolute paths. - publicPath: "/", - }), - new CopyWebpackPlugin({ - patterns: [ - { from: "./src/.nojekyll" }, - { from: "./src/manifest.json" }, - { from: "./src/favicon.ico" }, - { from: "./src/browserconfig.xml" }, - { from: "./src/app-id.json" }, - { from: "./src/images", to: "images" }, - { from: "./src/videos", to: "videos" }, - { from: "./src/locales", to: "locales" }, - { from: "../../node_modules/qrious/dist/qrious.min.js", to: "scripts" }, - { from: "../../node_modules/braintree-web-drop-in/dist/browser/dropin.js", to: "scripts" }, - { - from: "./src/version.json", - transform(content, path) { - return content.toString().replace("process.env.APPLICATION_VERSION", pjson.version); - }, - }, - ], - }), - new MiniCssExtractPlugin({ - filename: "[name].[contenthash].css", - chunkFilename: "[id].[contenthash].css", - }), - new webpack.ProvidePlugin({ - process: "process/browser.js", - }), - new webpack.EnvironmentPlugin({ - ENV: ENV, - NODE_ENV: NODE_ENV === "production" ? "production" : "development", - APPLICATION_VERSION: pjson.version, - CACHE_TAG: Math.random().toString(36).substring(7), - URLS: envConfig["urls"] ?? {}, - STRIPE_KEY: envConfig["stripeKey"] ?? "", - BRAINTREE_KEY: envConfig["braintreeKey"] ?? "", - PAYPAL_CONFIG: envConfig["paypal"] ?? {}, - FLAGS: envConfig["flags"] ?? {}, - DEV_FLAGS: NODE_ENV === "development" ? envConfig["devFlags"] : {}, - ADDITIONAL_REGIONS: envConfig["additionalRegions"] ?? [], - }), - new AngularWebpackPlugin({ - tsconfig: "tsconfig.build.json", +module.exports = buildConfig({ + configName: "OSS", + app: { + entry: "./src/main.ts", entryModule: "src/app/app.module#AppModule", - sourceMap: true, - }), -]; - -// ref: https://webpack.js.org/configuration/dev-server/#devserver -let certSuffix = fs.existsSync("dev-server.local.pem") ? ".local" : ".shared"; -const devServer = - NODE_ENV !== "development" - ? {} - : { - server: { - type: "https", - options: { - key: fs.readFileSync("dev-server" + certSuffix + ".pem"), - cert: fs.readFileSync("dev-server" + certSuffix + ".pem"), - }, - }, - // host: '192.168.1.9', - proxy: [ - { - context: ["/api"], - target: envConfig.dev?.proxyApi, - pathRewrite: { "^/api": "" }, - secure: false, - changeOrigin: true, - }, - { - context: ["/identity"], - target: envConfig.dev?.proxyIdentity, - pathRewrite: { "^/identity": "" }, - secure: false, - changeOrigin: true, - }, - { - context: ["/events"], - target: envConfig.dev?.proxyEvents, - pathRewrite: { "^/events": "" }, - secure: false, - changeOrigin: true, - }, - { - context: ["/notifications"], - target: envConfig.dev?.proxyNotifications, - pathRewrite: { "^/notifications": "" }, - secure: false, - changeOrigin: true, - ws: true, - }, - { - context: ["/icons"], - target: envConfig.dev?.proxyIcons, - pathRewrite: { "^/icons": "" }, - secure: false, - changeOrigin: true, - }, - ], - headers: (req) => { - if (!req.originalUrl.includes("connector.html")) { - return { - "Content-Security-Policy": ` - default-src 'self' - ;script-src - 'self' - 'wasm-unsafe-eval' - 'sha256-ryoU+5+IUZTuUyTElqkrQGBJXr1brEv6r2CA62WUw8w=' - https://js.stripe.com - https://js.braintreegateway.com - https://www.paypalobjects.com - ;style-src - 'self' - https://assets.braintreegateway.com - https://*.paypal.com - ${"'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='" /* date input polyfill */} - ${"'sha256-JVRXyYPueLWdwGwY9m/7u4QlZ1xeQdqUj2t8OVIzZE4='" /* date input polyfill */} - ${"'sha256-EnIJNDxVnh0++RytXJOkU0sqtLDFt1nYUDOfeJ5SKxg='" /* ng-select */} - ${"'sha256-dbBsIsz2pJ5loaLjhE6xWlmhYdjl6ghbwnGSCr4YObs='" /* cdk-virtual-scroll */} - ${"'sha256-S+uMh1G1SNQDAMG3seBmknQ26Wh+KSEoKdsNiy0joEE='" /* cdk-visually-hidden */} - ;img-src - 'self' - data: - https://icons.bitwarden.net - https://*.paypal.com - https://www.paypalobjects.com - https://q.stripe.com - https://haveibeenpwned.com - ;media-src - 'self' - https://assets.bitwarden.com - ;child-src - 'self' - https://js.stripe.com - https://assets.braintreegateway.com - https://*.paypal.com - https://*.duosecurity.com - ;frame-src - 'self' - https://js.stripe.com - https://assets.braintreegateway.com - https://*.paypal.com - https://*.duosecurity.com - ;connect-src - 'self' - ${envConfig.dev.wsConnectSrc ?? ""} - wss://notifications.bitwarden.com - https://notifications.bitwarden.com - https://cdn.bitwarden.net - https://api.pwnedpasswords.com - https://api.2fa.directory/v3/totp.json - https://api.stripe.com - https://www.paypal.com - https://api.sandbox.braintreegateway.com - https://api.braintreegateway.com - https://client-analytics.braintreegateway.com - https://*.braintree-api.com - https://*.blob.core.windows.net - http://127.0.0.1:10000 - https://app.simplelogin.io/api/alias/random/new - https://quack.duckduckgo.com/api/email/addresses - https://app.addy.io/api/v1/aliases - https://api.fastmail.com - https://api.forwardemail.net - http://localhost:5000 - ;object-src - 'self' - blob: - ;` - .replace(/\n/g, " ") - .replace(/ +(?= )/g, ""), - }; - } - }, - hot: false, - port: envConfig.dev?.port ?? 8080, - allowedHosts: envConfig.dev?.allowedHosts ?? "auto", - client: { - overlay: { - errors: true, - warnings: false, - runtimeErrors: false, - }, - }, - }; - -const webpackConfig = { - mode: NODE_ENV, - devtool: "source-map", - devServer: devServer, - target: "web", - entry: { - "app/polyfills": "./src/polyfills.ts", - "app/main": "./src/main.ts", - "connectors/webauthn": "./src/connectors/webauthn.ts", - "connectors/webauthn-fallback": "./src/connectors/webauthn-fallback.ts", - "connectors/sso": "./src/connectors/sso.ts", - "connectors/duo-redirect": "./src/connectors/duo-redirect.ts", - "connectors/redirect": "./src/connectors/redirect.ts", - styles: ["./src/scss/styles.scss", "./src/scss/tailwind.css"], - theme_head: "./src/theme.ts", }, - cache: - NODE_ENV === "production" - ? false - : { - type: "filesystem", - allowCollectingMemory: true, - cacheDirectory: path.resolve(__dirname, "../../node_modules/.cache/webpack"), - buildDependencies: { - config: [__filename], - }, - }, - snapshot: { - unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")], - }, - optimization: { - splitChunks: { - cacheGroups: { - commons: { - test: /[\\/]node_modules[\\/]/, - name: "app/vendor", - chunks: (chunk) => { - return chunk.name === "app/main"; - }, - }, - }, - }, - minimize: NODE_ENV === "production", - minimizer: [ - new TerserPlugin({ - terserOptions: { - safari10: true, - // Replicate Angular CLI behaviour - compress: { - global_defs: { - ngDevMode: false, - ngI18nClosureMode: false, - }, - }, - }, - }), - ], - }, - resolve: { - extensions: [".ts", ".js"], - symlinks: false, - modules: [path.resolve("../../node_modules")], - fallback: { - buffer: false, - util: require.resolve("util/"), - assert: false, - url: false, - fs: false, - process: false, - path: require.resolve("path-browserify"), - }, - }, - output: { - filename: "[name].[contenthash].js", - path: path.resolve(__dirname, "build"), - clean: true, - }, - module: { - rules: moduleRules, - }, - experiments: { - asyncWebAssembly: true, - }, - plugins: plugins, -}; - -module.exports = webpackConfig; + tsConfig: "tsconfig.build.json", +}); diff --git a/bitwarden_license/bit-cli/webpack.config.js b/bitwarden_license/bit-cli/webpack.config.js index 3e991f7971e..1dd54ae402c 100644 --- a/bitwarden_license/bit-cli/webpack.config.js +++ b/bitwarden_license/bit-cli/webpack.config.js @@ -1,12 +1,7 @@ -const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); +const { buildConfig } = require("../../apps/cli/webpack.base"); -// Re-use the OSS CLI webpack config -const webpackConfig = require("../../apps/cli/webpack.config"); - -// Update paths to use the bit-cli entrypoint and tsconfig -webpackConfig.entry = { bw: "../../bitwarden_license/bit-cli/src/bw.ts" }; -webpackConfig.resolve.plugins = [ - new TsconfigPathsPlugin({ configFile: "../../bitwarden_license/bit-cli/tsconfig.json" }), -]; - -module.exports = webpackConfig; +module.exports = buildConfig({ + configName: "Commercial", + entry: "../../bitwarden_license/bit-cli/src/bw.ts", + tsConfig: "../../bitwarden_license/bit-cli/tsconfig.json", +}); diff --git a/bitwarden_license/bit-web/webpack.config.js b/bitwarden_license/bit-web/webpack.config.js index ce5f0075afc..37e0a0c5e03 100644 --- a/bitwarden_license/bit-web/webpack.config.js +++ b/bitwarden_license/bit-web/webpack.config.js @@ -1,12 +1,10 @@ -const { AngularWebpackPlugin } = require("@ngtools/webpack"); +const { buildConfig } = require("../../apps/web/webpack.base"); -const webpackConfig = require("../../apps/web/webpack.config"); - -webpackConfig.entry["app/main"] = "../../bitwarden_license/bit-web/src/main.ts"; -webpackConfig.plugins[webpackConfig.plugins.length - 1] = new AngularWebpackPlugin({ - tsconfig: "../../bitwarden_license/bit-web/tsconfig.build.json", - entryModule: "bitwarden_license/src/app/app.module#AppModule", - sourceMap: true, +module.exports = buildConfig({ + configName: "Commercial", + app: { + entry: "../../bitwarden_license/bit-web/src/main.ts", + entryModule: "../../bitwarden_license/bit-web/src/app/app.module#AppModule", + }, + tsConfig: "../../bitwarden_license/bit-web/tsconfig.build.json", }); - -module.exports = webpackConfig; From aa3be491d78432ea1fe5dc37018cb696d2a6c129 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:38:17 -0700 Subject: [PATCH 75/83] Re-enable CI to run rust unit tests in desktop_native on Windows platform (#16711) * Re-enable CI to run rust unit tests in desktop_native on Windows platform * selectively exclude napi crate * use proper package name --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64c4e0dff13..2770c1257ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -125,8 +125,8 @@ jobs: - name: Test Windows if: ${{ matrix.os=='windows-2022'}} - working-directory: ./apps/desktop/desktop_native/core - run: cargo test -- --test-threads=1 + working-directory: ./apps/desktop/desktop_native + run: cargo test --workspace --exclude=desktop_napi -- --test-threads=1 rust-coverage: name: Rust Coverage From 24e159250be5c7b61e0876cf5d990f212c429bc5 Mon Sep 17 00:00:00 2001 From: Miles Blackwood <mrobinson@bitwarden.com> Date: Mon, 6 Oct 2025 18:22:07 -0400 Subject: [PATCH 76/83] Streamlines change pass notification logic. (#16435) * Streamlines change pass notification logic. * Includes test cases for all change password behavior. * Allows multiple cipher views to be passed to notification. * Removes assumptions about matching passwords. * Includes current password match for now. * [WIP] Fixes exact login match ignore for change. Partially updates save/update methods for ciphers. * Removes password matching. * Preserves nullable cipherId, set only while cipher action handled * Updates comment. --- .../abstractions/notification.background.ts | 2 +- .../notification.background.spec.ts | 223 ++++++++++++------ .../background/notification.background.ts | 140 +++++++---- .../overlay-notifications.background.ts | 4 +- .../components/cipher/cipher-action.ts | 13 +- .../content/components/cipher/cipher-item.ts | 1 + .../components/signals/selected-cipher.ts | 3 + apps/browser/src/autofill/notification/bar.ts | 25 +- 8 files changed, 276 insertions(+), 135 deletions(-) create mode 100644 apps/browser/src/autofill/content/components/signals/selected-cipher.ts diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index 52720b1f9f5..912d9657124 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -35,7 +35,7 @@ interface NotificationQueueMessage { } type ChangePasswordNotificationData = { - cipherId: CipherView["id"]; + cipherIds: CipherView["id"][]; newPassword: string; }; diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 032baf2e32b..507789ae7a4 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -289,7 +289,6 @@ describe("NotificationBackground", () => { let tab: chrome.tabs.Tab; let sender: chrome.runtime.MessageSender; let getEnableAddedLoginPromptSpy: jest.SpyInstance; - let getEnableChangedPasswordPromptSpy: jest.SpyInstance; let pushAddLoginToQueueSpy: jest.SpyInstance; let pushChangePasswordToQueueSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; @@ -306,10 +305,7 @@ describe("NotificationBackground", () => { notificationBackground as any, "getEnableAddedLoginPrompt", ); - getEnableChangedPasswordPromptSpy = jest.spyOn( - notificationBackground as any, - "getEnableChangedPasswordPrompt", - ); + pushAddLoginToQueueSpy = jest.spyOn(notificationBackground as any, "pushAddLoginToQueue"); pushChangePasswordToQueueSpy = jest.spyOn( notificationBackground as any, @@ -368,24 +364,6 @@ describe("NotificationBackground", () => { expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); }); - it("skips attempting to change the password for an existing login if the user has disabled changing the password notification", async () => { - const data: ModifyLoginCipherFormData = mockModifyLoginCipherFormData; - activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); - getEnableAddedLoginPromptSpy.mockReturnValueOnce(true); - getEnableChangedPasswordPromptSpy.mockReturnValueOnce(false); - getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock<CipherView>({ login: { username: "test", password: "oldPassword" } }), - ]); - - await notificationBackground.triggerAddLoginNotification(data, tab); - - expect(getEnableAddedLoginPromptSpy).toHaveBeenCalled(); - expect(getAllDecryptedForUrlSpy).toHaveBeenCalled(); - expect(getEnableChangedPasswordPromptSpy).toHaveBeenCalled(); - expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled(); - expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); - }); - it("skips attempting to change the password for an existing login if the password has not changed", async () => { const data: ModifyLoginCipherFormData = mockModifyLoginCipherFormData; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); @@ -445,37 +423,12 @@ describe("NotificationBackground", () => { sender.tab, ); }); - - it("adds a change password message to the queue if the user has changed an existing cipher's password", async () => { - const data: ModifyLoginCipherFormData = { - ...mockModifyLoginCipherFormData, - username: "tEsT", - }; - - activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); - getEnableAddedLoginPromptSpy.mockResolvedValueOnce(true); - getEnableChangedPasswordPromptSpy.mockResolvedValueOnce(true); - getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock<CipherView>({ - id: "cipher-id", - login: { username: "test", password: "oldPassword" }, - }), - ]); - - await notificationBackground.triggerAddLoginNotification(data, tab); - - expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( - "cipher-id", - "example.com", - data.password, - sender.tab, - ); - }); }); describe("bgTriggerChangedPasswordNotification message handler", () => { let tab: chrome.tabs.Tab; let sender: chrome.runtime.MessageSender; + let getEnableChangedPasswordPromptSpy: jest.SpyInstance; let pushChangePasswordToQueueSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; const mockModifyLoginCipherFormData: ModifyLoginCipherFormData = { @@ -488,6 +441,11 @@ describe("NotificationBackground", () => { beforeEach(() => { tab = createChromeTabMock(); sender = mock<chrome.runtime.MessageSender>({ tab }); + getEnableChangedPasswordPromptSpy = jest.spyOn( + notificationBackground as any, + "getEnableChangedPasswordPrompt", + ); + pushChangePasswordToQueueSpy = jest.spyOn( notificationBackground as any, "pushChangePasswordToQueue", @@ -495,6 +453,40 @@ describe("NotificationBackground", () => { getAllDecryptedForUrlSpy = jest.spyOn(cipherService, "getAllDecryptedForUrl"); }); + afterEach(() => { + getEnableChangedPasswordPromptSpy.mockRestore(); + pushChangePasswordToQueueSpy.mockRestore(); + getAllDecryptedForUrlSpy.mockRestore(); + }); + + it("skips attempting to change the password for an existing login if the user has disabled changing the password notification", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + }; + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getEnableChangedPasswordPromptSpy.mockReturnValueOnce(false); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock<CipherView>({ login: { username: "test", password: "oldPassword" } }), + ]); + + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); + }); + + it("skips attempting to add the change password message to the queue if the user is logged out", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + }; + + activeAccountStatusMock$.next(AuthenticationStatus.LoggedOut); + + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); + }); + it("skips attempting to add the change password message to the queue if the passed url is not valid", async () => { const data: ModifyLoginCipherFormData = mockModifyLoginCipherFormData; @@ -503,7 +495,92 @@ describe("NotificationBackground", () => { expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); }); - it("adds a change password message to the queue if the user does not have an unlocked account", async () => { + it("only only includes ciphers in notification data matching a username if username was present in the modify form data", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + username: "userName", + }; + + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock<CipherView>({ + id: "cipher-id-1", + login: { username: "test", password: "currentPassword" }, + }), + mock<CipherView>({ + id: "cipher-id-2", + login: { username: "username", password: "currentPassword" }, + }), + mock<CipherView>({ + id: "cipher-id-3", + login: { username: "uSeRnAmE", password: "currentPassword" }, + }), + ]); + + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-2", "cipher-id-3"], + "example.com", + data?.newPassword, + sender.tab, + ); + }); + + it("adds a change password message to the queue with current password, if there is a current password, but no new password", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + password: "newPasswordUpdatedElsewhere", + newPassword: null, + }; + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock<CipherView>({ + id: "cipher-id-1", + login: { password: "currentPassword" }, + }), + ]); + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-1"], + "example.com", + data?.password, + sender.tab, + ); + }); + + it("adds a change password message to the queue with new password, if new password is provided", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + password: "password2", + newPassword: "password3", + }; + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock<CipherView>({ + id: "cipher-id-1", + login: { password: "password1" }, + }), + mock<CipherView>({ + id: "cipher-id-4", + login: { password: "password4" }, + }), + ]); + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-1", "cipher-id-4"], + "example.com", + data?.newPassword, + sender.tab, + ); + }); + + it("adds a change password message to the queue if the user has a locked account", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, uri: "https://example.com", @@ -522,10 +599,12 @@ describe("NotificationBackground", () => { ); }); - it("skips adding a change password message to the queue if the multiple ciphers exist for the passed URL and the current password is not found within the list of ciphers", async () => { + it("doesn't add a password if there is no current or new password", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, uri: "https://example.com", + password: null, + newPassword: null, }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); getAllDecryptedForUrlSpy.mockResolvedValueOnce([ @@ -537,23 +616,6 @@ describe("NotificationBackground", () => { expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); }); - it("skips adding a change password message if more than one existing cipher is found with a matching password ", async () => { - const data: ModifyLoginCipherFormData = { - ...mockModifyLoginCipherFormData, - uri: "https://example.com", - }; - activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); - getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock<CipherView>({ login: { username: "test", password: "password" } }), - mock<CipherView>({ login: { username: "test2", password: "password" } }), - ]); - - await notificationBackground.triggerChangedPasswordNotification(data, tab); - - expect(getAllDecryptedForUrlSpy).toHaveBeenCalled(); - expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); - }); - it("adds a change password message to the queue if a single cipher matches the passed current password", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, @@ -570,28 +632,39 @@ describe("NotificationBackground", () => { await notificationBackground.triggerChangedPasswordNotification(data, tab); expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( - "cipher-id", + ["cipher-id"], "example.com", data?.newPassword, sender.tab, ); }); - it("skips adding a change password message if no current password is passed in the message and more than one cipher is found for a url", async () => { + it("adds a change password message with all matching ciphers if no current password is passed and more than one cipher is found for a url", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, uri: "https://example.com", + password: null, }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock<CipherView>({ login: { username: "test", password: "password" } }), - mock<CipherView>({ login: { username: "test2", password: "password" } }), + mock<CipherView>({ + id: "cipher-id-1", + login: { username: "test", password: "password" }, + }), + mock<CipherView>({ + id: "cipher-id-2", + login: { username: "test2", password: "password" }, + }), ]); await notificationBackground.triggerChangedPasswordNotification(data, tab); - expect(getAllDecryptedForUrlSpy).toHaveBeenCalled(); - expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-1", "cipher-id-2"], + "example.com", + data?.newPassword, + sender.tab, + ); }); it("adds a change password message to the queue if no current password is passed with the message, but a single cipher is matched for the uri", async () => { @@ -611,7 +684,7 @@ describe("NotificationBackground", () => { await notificationBackground.triggerChangedPasswordNotification(data, tab); expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( - "cipher-id", + ["cipher-id"], "example.com", data?.newPassword, sender.tab, diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index d44bf2f1507..5bfb12f1932 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -213,14 +213,26 @@ export default class NotificationBackground { let cipherView: CipherView; if (cipherQueueMessage.type === NotificationType.ChangePassword) { const { - data: { cipherId }, + data: { cipherIds }, } = cipherQueueMessage; - cipherView = await this.getDecryptedCipherById(cipherId, activeUserId); + const cipherViews = await this.cipherService.getAllDecrypted(activeUserId); + return cipherViews + .filter((cipher) => cipherIds.includes(cipher.id)) + .map((cipherView) => { + const organizationType = getOrganizationType(cipherView.organizationId); + return this.convertToNotificationCipherData( + cipherView, + iconsServerUrl, + showFavicons, + organizationType, + ); + }); } else { cipherView = this.convertAddLoginQueueMessageToCipherView(cipherQueueMessage); } const organizationType = getOrganizationType(cipherView.organizationId); + return [ this.convertToNotificationCipherData( cipherView, @@ -555,16 +567,6 @@ export default class NotificationBackground { return true; } - const changePasswordIsEnabled = await this.getEnableChangedPasswordPrompt(); - - if ( - changePasswordIsEnabled && - usernameMatches.length === 1 && - usernameMatches[0].login.password !== login.password - ) { - await this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, login.password, tab); - return true; - } return false; } @@ -603,45 +605,92 @@ export default class NotificationBackground { data: ModifyLoginCipherFormData, tab: chrome.tabs.Tab, ): Promise<boolean> { - const changeData = { - url: data.uri, - currentPassword: data.password, - newPassword: data.newPassword, - }; - - const loginDomain = Utils.getDomain(changeData.url); - if (loginDomain == null) { + const changePasswordIsEnabled = await this.getEnableChangedPasswordPrompt(); + if (!changePasswordIsEnabled) { return false; } - - if ((await this.getAuthStatus()) < AuthenticationStatus.Unlocked) { - await this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true); - return true; + const authStatus = await this.getAuthStatus(); + if (authStatus === AuthenticationStatus.LoggedOut) { + return false; } - - let id: string = null; const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getOptionalUserId), ); - if (activeUserId == null) { + if (activeUserId === null) { + return false; + } + const loginDomain = Utils.getDomain(data.uri); + if (loginDomain === null) { return false; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId); - if (changeData.currentPassword != null) { - const passwordMatches = ciphers.filter( - (c) => c.login.password === changeData.currentPassword, - ); - if (passwordMatches.length === 1) { - id = passwordMatches[0].id; - } - } else if (ciphers.length === 1) { - id = ciphers[0].id; - } - if (id != null) { - await this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab); + const username: string | null = data.username || null; + const currentPassword = data.password || null; + const newPassword = data.newPassword || null; + + if (authStatus === AuthenticationStatus.Locked && newPassword !== null) { + await this.pushChangePasswordToQueue(null, loginDomain, newPassword, tab, true); return true; } + + let ciphers: CipherView[] = await this.cipherService.getAllDecryptedForUrl( + data.uri, + activeUserId, + ); + + const normalizedUsername: string = username ? username.toLowerCase() : ""; + + const shouldMatchUsername = typeof username === "string" && username.length > 0; + + if (shouldMatchUsername) { + // Presence of a username should filter ciphers further. + ciphers = ciphers.filter( + (cipher) => + cipher.login.username !== null && + cipher.login.username.toLowerCase() === normalizedUsername, + ); + } + + if (ciphers.length === 1) { + const [cipher] = ciphers; + if ( + username !== null && + newPassword === null && + cipher.login.username === normalizedUsername && + cipher.login.password === currentPassword + ) { + // Assumed to be a login + return false; + } + } + + if (currentPassword && !newPassword) { + // Only use current password for change if no new password present. + if (ciphers.length > 0) { + await this.pushChangePasswordToQueue( + ciphers.map((cipher) => cipher.id), + loginDomain, + currentPassword, + tab, + ); + return true; + } + } + + if (newPassword) { + // Otherwise include all known ciphers. + if (ciphers.length > 0) { + await this.pushChangePasswordToQueue( + ciphers.map((cipher) => cipher.id), + loginDomain, + newPassword, + tab, + ); + + return true; + } + } + return false; } @@ -666,7 +715,7 @@ export default class NotificationBackground { } private async pushChangePasswordToQueue( - cipherId: string, + cipherIds: CipherView["id"][], loginDomain: string, newPassword: string, tab: chrome.tabs.Tab, @@ -677,7 +726,7 @@ export default class NotificationBackground { const launchTimestamp = new Date().getTime(); const message: AddChangePasswordNotificationQueueMessage = { type: NotificationType.ChangePassword, - data: { cipherId: cipherId, newPassword: newPassword }, + data: { cipherIds: cipherIds, newPassword: newPassword }, domain: loginDomain, tab: tab, launchTimestamp, @@ -716,12 +765,12 @@ export default class NotificationBackground { return; } - await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder); + await this.saveOrUpdateCredentials(sender.tab, message.cipherId, message.edit, message.folder); } async handleCipherUpdateRepromptResponse(message: NotificationBackgroundExtensionMessage) { if (message.success) { - await this.saveOrUpdateCredentials(message.tab, false, undefined, true); + await this.saveOrUpdateCredentials(message.tab, message.cipherId, false, undefined, true); } else { await BrowserApi.tabSendMessageData(message.tab, "saveCipherAttemptCompleted", { error: "Password reprompt failed", @@ -740,6 +789,7 @@ export default class NotificationBackground { */ private async saveOrUpdateCredentials( tab: chrome.tabs.Tab, + cipherId: CipherView["id"], edit: boolean, folderId?: string, skipReprompt: boolean = false, @@ -764,7 +814,7 @@ export default class NotificationBackground { if (queueMessage.type === NotificationType.ChangePassword) { const { - data: { cipherId, newPassword }, + data: { newPassword }, } = queueMessage; const cipherView = await this.getDecryptedCipherById(cipherId, activeUserId); diff --git a/apps/browser/src/autofill/background/overlay-notifications.background.ts b/apps/browser/src/autofill/background/overlay-notifications.background.ts index 4657dfb6d1f..e08fe540710 100644 --- a/apps/browser/src/autofill/background/overlay-notifications.background.ts +++ b/apps/browser/src/autofill/background/overlay-notifications.background.ts @@ -455,12 +455,12 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg notificationType: NotificationType, ): boolean => { switch (notificationType) { - case NotificationTypes.Change: - return modifyLoginData?.newPassword && !modifyLoginData.username; case NotificationTypes.Add: return ( modifyLoginData?.username && !!(modifyLoginData.password || modifyLoginData.newPassword) ); + case NotificationTypes.Change: + return !!(modifyLoginData.password || modifyLoginData.newPassword); case NotificationTypes.AtRiskPassword: return !modifyLoginData.newPassword; case NotificationTypes.Unlock: diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts index 34ad5e1c9a9..7a392849996 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts @@ -4,8 +4,10 @@ import { BadgeButton } from "../../../content/components/buttons/badge-button"; import { EditButton } from "../../../content/components/buttons/edit-button"; import { NotificationTypes } from "../../../notification/abstractions/notification-bar"; import { I18n } from "../common-types"; +import { selectedCipher as selectedCipherSignal } from "../signals/selected-cipher"; export type CipherActionProps = { + cipherId: string; handleAction?: (e: Event) => void; i18n: I18n; itemName: string; @@ -15,6 +17,7 @@ export type CipherActionProps = { }; export function CipherAction({ + cipherId, handleAction = () => { /* no-op */ }, @@ -24,9 +27,17 @@ export function CipherAction({ theme, username, }: CipherActionProps) { + const selectCipherHandleAction = (e: Event) => { + selectedCipherSignal.set(cipherId); + try { + handleAction(e); + } finally { + selectedCipherSignal.set(null); + } + }; return notificationType === NotificationTypes.Change ? BadgeButton({ - buttonAction: handleAction, + buttonAction: selectCipherHandleAction, buttonText: i18n.notificationUpdate, itemName, theme, diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts index ab3b57f535c..3bfc44636b3 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts @@ -40,6 +40,7 @@ export function CipherItem({ if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { cipherActionButton = html`<div> ${CipherAction({ + cipherId: cipher.id, handleAction, i18n, itemName: name, diff --git a/apps/browser/src/autofill/content/components/signals/selected-cipher.ts b/apps/browser/src/autofill/content/components/signals/selected-cipher.ts new file mode 100644 index 00000000000..360457233f5 --- /dev/null +++ b/apps/browser/src/autofill/content/components/signals/selected-cipher.ts @@ -0,0 +1,3 @@ +import { signal } from "@lit-labs/signals"; + +export const selectedCipher = signal<string | null>(null); diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 9ae6fcedc8f..fcf91ca2e91 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -1,6 +1,7 @@ import { render } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { NotificationCipherData } from "../content/components/cipher/types"; @@ -8,6 +9,7 @@ import { CollectionView, I18n, OrgView } from "../content/components/common-type import { AtRiskNotification } from "../content/components/notification/at-risk-password/container"; import { NotificationConfirmationContainer } from "../content/components/notification/confirmation/container"; import { NotificationContainer } from "../content/components/notification/container"; +import { selectedCipher as selectedCipherSignal } from "../content/components/signals/selected-cipher"; import { selectedFolder as selectedFolderSignal } from "../content/components/signals/selected-folder"; import { selectedVault as selectedVaultSignal } from "../content/components/signals/selected-vault"; @@ -180,9 +182,9 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { const i18n = getI18n(); const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light); - const resolvedType = resolveNotificationType(notificationBarIframeInitData); - const headerMessage = getNotificationHeaderMessage(i18n, resolvedType); - const notificationTestId = getNotificationTestId(resolvedType); + const notificationType = resolveNotificationType(notificationBarIframeInitData); + const headerMessage = getNotificationHeaderMessage(i18n, notificationType); + const notificationTestId = getNotificationTestId(notificationType); appendHeaderMessageToTitle(headerMessage); document.body.innerHTML = ""; @@ -191,7 +193,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { const notificationConfig = { ...notificationBarIframeInitData, headerMessage, - type: resolvedType, + type: notificationType, notificationTestId, theme: resolvedTheme, personalVaultIsAllowed: !personalVaultDisallowed, @@ -201,7 +203,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { }; const handleSaveAction = () => { - sendSaveCipherMessage(true); + // cipher ID is null while vault is locked. + sendSaveCipherMessage(null, true); render( NotificationContainer({ @@ -262,7 +265,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { NotificationContainer({ ...notificationBarIframeInitData, headerMessage, - type: resolvedType, + type: notificationType, theme: resolvedTheme, notificationTestId, personalVaultIsAllowed: !personalVaultDisallowed, @@ -276,9 +279,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { }); function handleEditOrUpdateAction(e: Event) { - const notificationType = initData?.type; e.preventDefault(); - notificationType === "add" ? sendSaveCipherMessage(true) : sendSaveCipherMessage(false); + sendSaveCipherMessage(selectedCipherSignal.get(), notificationType === NotificationTypes.Add); } } @@ -291,6 +293,7 @@ function handleCloseNotification(e: Event) { } function handleSaveAction(e: Event) { + const selectedCipher = selectedCipherSignal.get(); const selectedVault = selectedVaultSignal.get(); const selectedFolder = selectedFolderSignal.get(); @@ -304,16 +307,16 @@ function handleSaveAction(e: Event) { } e.preventDefault(); - - sendSaveCipherMessage(removeIndividualVault(), selectedFolder); + sendSaveCipherMessage(selectedCipher, removeIndividualVault(), selectedFolder); if (removeIndividualVault()) { return; } } -function sendSaveCipherMessage(edit: boolean, folder?: string) { +function sendSaveCipherMessage(cipherId: CipherView["id"] | null, edit: boolean, folder?: string) { sendPlatformMessage({ command: "bgSaveCipher", + cipherId, folder, edit, }); From 5e5813523fba266d1028959972ff79c72739bd06 Mon Sep 17 00:00:00 2001 From: Colton Hurst <colton@coltonhurst.com> Date: Mon, 6 Oct 2025 19:54:12 -0400 Subject: [PATCH 77/83] [PM-26567] Refactor autotype transition key (#16763) --- apps/desktop/src/app/accounts/settings.component.html | 2 +- apps/desktop/src/locales/en/messages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 7fdca9ff29b..dfddff034e6 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -350,7 +350,7 @@ </div> <small class="help-block" *ngIf="form.value.enableAutotype"> <b>{{ "important" | i18n }}</b> - {{ "enableAutotypeDescriptionTransitionKey" | i18n }} + {{ "enableAutotypeShortcutDescription" | i18n }} <a class="settings-link" tabindex="0" diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 8bc0eb9ecc2..4d1bf8e15fc 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -4135,7 +4135,7 @@ "enableAutotypeShortcutPreview": { "message": "Enable autotype shortcut (Feature Preview)" }, - "enableAutotypeDescriptionTransitionKey": { + "enableAutotypeShortcutDescription": { "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." }, "editShortcut": { From bcc050d4de8f10994d9812732354af6aa326752f Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:17:07 +0200 Subject: [PATCH 78/83] [PM-26488][PM-26494] Disable chrome loader on MAS (#16699) * Disable chrome loader on MAS * Remove direct dependency on platform utils service in favour of the system * Move check above brave condition * Invert condition --- libs/importer/src/services/import.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index da080bacaad..351d89be3fa 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -146,7 +146,8 @@ export class ImportService implements ImportServiceAbstraction { map(([type, enabled]) => { let loaders = availableLoaders(type, client); - let isUnsupported = false; + // Mac App Store is currently disabled due to sandboxing. + let isUnsupported = this.system.environment.isMacAppStore(); if (enabled && type === "bravecsv") { try { From ddc840027ac6895bb4e376601535a73f5c08ee05 Mon Sep 17 00:00:00 2001 From: Addison Beck <github@addisonbeck.com> Date: Tue, 7 Oct 2025 09:48:02 -0400 Subject: [PATCH 79/83] build(cli): integrate nx (#16648) * build(cli): integrate nx * refactor(project.json): rename "bit" builds to "commercial" * refactor(webpack.base): implement DEFAULT_PARAMS * refactor(webpack.base): move DEFAULT_PARAMS out of buildConfig --- .github/renovate.json5 | 1 + apps/cli/project.json | 86 + apps/cli/webpack.base.js | 36 +- apps/cli/webpack.config.js | 51 +- bitwarden_license/bit-cli/webpack.config.js | 51 +- docs/using-nx-to-build-projects.md | 208 ++ package-lock.json | 2032 ++++++++++++++++--- package.json | 1 + 8 files changed, 2134 insertions(+), 332 deletions(-) create mode 100644 apps/cli/project.json create mode 100644 docs/using-nx-to-build-projects.md diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8e31ab7a384..126d66b34e3 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -147,6 +147,7 @@ "@nx/eslint", "@nx/jest", "@nx/js", + "@nx/webpack", "@types/chrome", "@types/firefox-webext-browser", "@types/glob", diff --git a/apps/cli/project.json b/apps/cli/project.json new file mode 100644 index 00000000000..229738818a7 --- /dev/null +++ b/apps/cli/project.json @@ -0,0 +1,86 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "cli", + "projectType": "application", + "sourceRoot": "apps/cli/src", + "tags": ["scope:cli", "type:app"], + "targets": { + "build": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "oss-dev", + "options": { + "outputPath": "dist/apps/cli", + "webpackConfig": "apps/cli/webpack.config.js", + "tsConfig": "apps/cli/tsconfig.json", + "main": "apps/cli/src/bw.ts", + "target": "node", + "compiler": "tsc" + }, + "configurations": { + "oss": { + "mode": "production", + "outputPath": "dist/apps/cli/oss" + }, + "oss-dev": { + "mode": "development", + "outputPath": "dist/apps/cli/oss-dev" + }, + "commercial": { + "mode": "production", + "outputPath": "dist/apps/cli/commercial", + "webpackConfig": "bitwarden_license/bit-cli/webpack.config.js", + "main": "bitwarden_license/bit-cli/src/bw.ts", + "tsConfig": "bitwarden_license/bit-cli/tsconfig.json" + }, + "commercial-dev": { + "mode": "development", + "outputPath": "dist/apps/cli/commercial-dev", + "webpackConfig": "bitwarden_license/bit-cli/webpack.config.js", + "main": "bitwarden_license/bit-cli/src/bw.ts", + "tsConfig": "bitwarden_license/bit-cli/tsconfig.json" + } + } + }, + "serve": { + "executor": "@nx/webpack:webpack", + "defaultConfiguration": "oss-dev", + "options": { + "outputPath": "dist/apps/cli", + "webpackConfig": "apps/cli/webpack.config.js", + "tsConfig": "apps/cli/tsconfig.json", + "main": "apps/cli/src/bw.ts", + "target": "node", + "compiler": "tsc", + "watch": true + }, + "configurations": { + "oss-dev": { + "mode": "development", + "outputPath": "dist/apps/cli/oss-dev" + }, + "commercial-dev": { + "mode": "development", + "outputPath": "dist/apps/cli/commercial-dev", + "webpackConfig": "bitwarden_license/bit-cli/webpack.config.js", + "main": "bitwarden_license/bit-cli/src/bw.ts", + "tsConfig": "bitwarden_license/bit-cli/tsconfig.json" + } + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/cli/jest.config.js" + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/cli/**/*.ts"] + } + } + } +} diff --git a/apps/cli/webpack.base.js b/apps/cli/webpack.base.js index b937a4b9641..01d5fc5b175 100644 --- a/apps/cli/webpack.base.js +++ b/apps/cli/webpack.base.js @@ -10,16 +10,32 @@ module.exports.getEnv = function getEnv() { return { ENV }; }; +const DEFAULT_PARAMS = { + localesPath: "./src/locales", + modulesPath: [path.resolve("../../node_modules")], + externalsModulesDir: "../../node_modules", + outputPath: path.resolve(__dirname, "build"), + watch: false, +}; + /** * * @param {{ * configName: string; * entry: string; * tsConfig: string; + * outputPath?: string; + * mode?: string; + * env?: string; + * modulesPath?: string[]; + * localesPath?: string; + * externalsModulesDir?: string; + * watch?: boolean; * }} params */ module.exports.buildConfig = function buildConfig(params) { - const { ENV } = module.exports.getEnv(); + params = { ...DEFAULT_PARAMS, ...params }; + const ENV = params.env || module.exports.getEnv().ENV; const envConfig = config.load(ENV); config.log(`Building CLI - ${params.configName} version`); @@ -35,7 +51,7 @@ module.exports.buildConfig = function buildConfig(params) { const plugins = [ new CopyWebpackPlugin({ - patterns: [{ from: "./src/locales", to: "locales" }], + patterns: [{ from: params.localesPath, to: "locales" }], }), new webpack.DefinePlugin({ "process.env.BWCLI_ENV": JSON.stringify(ENV), @@ -61,7 +77,7 @@ module.exports.buildConfig = function buildConfig(params) { ]; const webpackConfig = { - mode: ENV, + mode: params.mode || ENV, target: "node", devtool: ENV === "development" ? "eval-source-map" : "source-map", node: { @@ -77,19 +93,19 @@ module.exports.buildConfig = function buildConfig(params) { resolve: { extensions: [".ts", ".js"], symlinks: false, - modules: [path.resolve("../../node_modules")], + modules: params.modulesPath, plugins: [new TsconfigPathsPlugin({ configFile: params.tsConfig })], }, output: { filename: "[name].js", - path: path.resolve(__dirname, "build"), + path: path.resolve(params.outputPath), clean: true, }, module: { rules: moduleRules }, plugins: plugins, externals: [ nodeExternals({ - modulesDir: "../../node_modules", + modulesDir: params.externalsModulesDir, allowlist: [/@bitwarden/], }), ], @@ -97,6 +113,12 @@ module.exports.buildConfig = function buildConfig(params) { asyncWebAssembly: true, }, }; - + if (params.watch) { + webpackConfig.watch = true; + webpackConfig.watchOptions = { + ignored: /node_modules/, + poll: 1000, + }; + } return webpackConfig; }; diff --git a/apps/cli/webpack.config.js b/apps/cli/webpack.config.js index 7ac290402cc..b8eae3dce4d 100644 --- a/apps/cli/webpack.config.js +++ b/apps/cli/webpack.config.js @@ -1,7 +1,48 @@ +const path = require("path"); const { buildConfig } = require("./webpack.base"); -module.exports = buildConfig({ - configName: "OSS", - entry: "./src/bw.ts", - tsConfig: "./tsconfig.json", -}); +module.exports = (webpackConfig, context) => { + // Detect if called by Nx (context parameter exists) + const isNxBuild = context && context.options; + + if (isNxBuild) { + // Nx build configuration + const mode = context.options.mode || "development"; + if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = mode; + } + const ENV = (process.env.ENV = process.env.NODE_ENV); + + return buildConfig({ + configName: "OSS", + entry: context.options.main || "apps/cli/src/bw.ts", + tsConfig: "tsconfig.base.json", + outputPath: path.resolve(context.context.root, context.options.outputPath), + mode: mode, + env: ENV, + modulesPath: [path.resolve("node_modules")], + localesPath: "apps/cli/src/locales", + externalsModulesDir: "node_modules", + watch: context.options.watch || false, + }); + } else { + // npm build configuration + if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = "development"; + } + const ENV = (process.env.ENV = process.env.NODE_ENV); + const mode = ENV; + + return buildConfig({ + configName: "OSS", + entry: "./src/bw.ts", + tsConfig: "./tsconfig.json", + outputPath: path.resolve(__dirname, "build"), + mode: mode, + env: ENV, + modulesPath: [path.resolve("../../node_modules")], + localesPath: "./src/locales", + externalsModulesDir: "../../node_modules", + }); + } +}; diff --git a/bitwarden_license/bit-cli/webpack.config.js b/bitwarden_license/bit-cli/webpack.config.js index 1dd54ae402c..f746da40761 100644 --- a/bitwarden_license/bit-cli/webpack.config.js +++ b/bitwarden_license/bit-cli/webpack.config.js @@ -1,7 +1,48 @@ +const path = require("path"); const { buildConfig } = require("../../apps/cli/webpack.base"); -module.exports = buildConfig({ - configName: "Commercial", - entry: "../../bitwarden_license/bit-cli/src/bw.ts", - tsConfig: "../../bitwarden_license/bit-cli/tsconfig.json", -}); +module.exports = (webpackConfig, context) => { + // Detect if called by Nx (context parameter exists) + const isNxBuild = context && context.options; + + if (isNxBuild) { + // Nx build configuration + const mode = context.options.mode || "development"; + if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = mode; + } + const ENV = (process.env.ENV = process.env.NODE_ENV); + + return buildConfig({ + configName: "Commercial", + entry: context.options.main || "bitwarden_license/bit-cli/src/bw.ts", + tsConfig: "tsconfig.base.json", + outputPath: path.resolve(context.context.root, context.options.outputPath), + mode: mode, + env: ENV, + modulesPath: [path.resolve("node_modules")], + localesPath: "apps/cli/src/locales", + externalsModulesDir: "node_modules", + watch: context.options.watch || false, + }); + } else { + // npm build configuration + if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = "development"; + } + const ENV = (process.env.ENV = process.env.NODE_ENV); + const mode = ENV; + + return buildConfig({ + configName: "Commercial", + entry: "../../bitwarden_license/bit-cli/src/bw.ts", + tsConfig: "../../bitwarden_license/bit-cli/tsconfig.json", + outputPath: path.resolve(__dirname, "../../apps/cli/build"), + mode: mode, + env: ENV, + modulesPath: [path.resolve("../../node_modules")], + localesPath: "../../apps/cli/src/locales", + externalsModulesDir: "../../node_modules", + }); + } +}; diff --git a/docs/using-nx-to-build-projects.md b/docs/using-nx-to-build-projects.md new file mode 100644 index 00000000000..f1fd54e1c20 --- /dev/null +++ b/docs/using-nx-to-build-projects.md @@ -0,0 +1,208 @@ +# Using Nx to Build Projects + +Bitwarden uses [Nx](https://nx.dev/) to make building projects from the monorepo easier. To build, lint, or test a project you'll want to reference the project's `project.json` file for availible commands and their names. Then you'll run `npx nx [your_command] [your_project] [your_options]`. Run `npx nx --help` to see availible options, there are many. + +Please note: the Nx implementation is a work in progress. Not all apps support Nx yet, CI still uses the old npm builds, and we have many "legacy" libraries that use hacks to get them into the Nx project graph. + +## Quick Start + +### Basic Commands + +```bash +# Build a project +npx nx build cli +npx nx build state # Modern libs and apps have simple, all lowercase target names +npx nx build @bitwarden/common # Legacy libs have a special naming convention and include the @bitwarden prefix + +# Test a project +npx nx test cli + +# Lint a project +npx nx lint cli + +# Serve/watch a project (for projects with serve targets) +npx nx serve cli + +# Build all projects that differ from origin/main +nx affected --target=build --base=origin/main + +# Build, lint, and test every project at once +npx nx run-many --target=build,test,lint --all + +# Most projects default to the "oss-dev" build, so if you need the bitwarden license build add a --configuration +npx nx build cli --configuration=commercial-dev + +# If you need a production build drop the "dev" suffix +npx nx build cli --configuration=oss # or "commercial" + +# Configurations can also be passed to run-many +# For example: to run all Bitwarden licensed builds +npx nx run-many --target=build,test,lint --all --configuration=commercial + +# Outputs are distrubuted in a root level /dist/ folder + +# Run a locally built CLI +node dist/apps/cli/oss-dev/bw.js +``` + +### Global Commands + +```bash +# See all projects +npx nx show projects + +# Run affected projects only (great for local dev and CI) +npx nx affected:build +npx nx affected:test +npx nx affected:lint + +# Show dependency graph +npx nx dep-graph +``` + +## Library Projects + +Our libraries use two different Nx integration patterns depending on their migration status. + +### Legacy Libraries + +Most existing libraries use a facade pattern where `project.json` delegates to existing npm scripts. This approach maintains backward compatibility with the build methods we used before introducing Nx. These libraries are considered tech debt and Platform has a focus on updating them. For an example reference `libs/common/project.json`. + +These libraries use `nx:run-script` executor to call existing npm scripts: + +```json +{ + "targets": { + "build": { + "executor": "nx:run-script", + "options": { + "script": "build" + } + } + } +} +``` + +#### Available Commands for Legacy Libraries + +All legacy libraries support these standardized commands: + +- **`nx build <library>`** - Build the library +- **`nx build:watch <library>`** - Build and watch for changes +- **`nx clean <library>`** - Clean build artifacts +- **`nx test <library>`** - Run tests +- **`nx lint <library>`** - Run linting + +### Modern Libraries + +Newer libraries like `libs/state` use native Nx executors for better performance and caching. + +```json +{ + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/state" + } + } + } +} +``` + +## What Happens When You Run An Nx Command + +```mermaid +flowchart TD + Start([You just ran an nx command]) --> ParseCmd[Nx parses command args] + ParseCmd --> ReadWorkspace[Nx reads nx.json, workspace configuration, cache settings, and plugins] + ReadWorkspace --> ReadProject[Nx reads project.json, finds the target configuration, and checks executor to use] + ReadProject --> CheckCache{Nx checks the cache: has this exact build been done before?} + + CheckCache -->|Cache hit| UseCached[Nx uses cached outputs, copies from .nx/cache, and skips execution] + UseCached --> Done([Your command is done]) + + CheckCache -->|Cache miss| DetermineExecutor{Which executor is configured?} + + DetermineExecutor -->|nx:run-script| FacadePattern[Legacy Facade Pattern] + DetermineExecutor -->|nx/webpack:webpack| WebpackExecutor[Webpack Executor] + DetermineExecutor -->|nx/js:tsc| TypeScriptExecutor[TypeScript Executor] + DetermineExecutor -->|nx/jest:jest| JestExecutor[Jest Executor] + DetermineExecutor -->|nx/eslint:lint| ESLintExecutor[ESLint Executor] + + %% Facade Pattern Flow + FacadePattern --> ReadPackageJson[The run-script executor finds npm script to run in package.json] + ReadPackageJson --> RunNpmScript[Npm script is executed] + RunNpmScript --> NpmDelegates{What does the npm script do?} + + NpmDelegates -->|TypeScript| TSCompile[TypeScript compiles to JavaScript using tsconfig.json] + NpmDelegates -->|Webpack| WebpackBuild[Webpack bundles and optimizes code] + NpmDelegates -->|Jest| JestTest[Jest executes unit tests] + + TSCompile --> FacadeOutput[Outputs written to libs/LIB/dist/] + WebpackBuild --> FacadeOutput + JestTest --> FacadeOutput + FacadeOutput --> CacheResults1[Nx caches results in .nx/cache/] + + %% Webpack Executor Flow + WebpackExecutor --> ReadWebpackConfig[Webpack config read from apps/cli/webpack.config.js or bit-cli/webpack.config.js] + ReadWebpackConfig --> ConfigureWebpack[Webpack configured with entry points, TypeScript paths, and plugins] + ConfigureWebpack --> WebpackProcess[Webpack resolves paths, compiles TypeScript, bundles dependencies, and applies optimizations] + WebpackProcess --> WebpackOutput[Single executable bundle written to dist/apps/cli/] + WebpackOutput --> CacheResults2[Nx caches results in .nx/cache/] + + %% TypeScript Executor Flow + TypeScriptExecutor --> ReadTSConfig[TypeScript reads tsconfig.lib.json compilation options] + ReadTSConfig --> TSProcess[TypeScript performs type checking, emits declarations, and compiles to JavaScript] + TSProcess --> TSOutput[Outputs written to dist/libs/LIB/] + TSOutput --> CacheResults3[Nx caches results in .nx/cache/] + + %% Jest Executor Flow + JestExecutor --> ReadJestConfig[Jest reads jest.config.js test configuration] + ReadJestConfig --> JestProcess[Jest finds test files, runs suites, and generates coverage] + JestProcess --> JestOutput[Test results and coverage reports output] + JestOutput --> CacheResults4[Nx caches results in .nx/cache/] + + %% ESLint Executor Flow + ESLintExecutor --> ReadESLintConfig[ESLint reads .eslintrc.json rules and configuration] + ReadESLintConfig --> ESLintProcess[ESLint checks code style, finds issues, and applies auto-fixes] + ESLintProcess --> ESLintOutput[Lint results with errors and warnings output] + ESLintOutput --> CacheResults5[Nx caches results in .nx/cache/] + + %% All paths converge + CacheResults1 --> UpdateGraph[Dependency graph updated to track project relationships] + CacheResults2 --> UpdateGraph + CacheResults3 --> UpdateGraph + CacheResults4 --> UpdateGraph + CacheResults5 --> UpdateGraph + + UpdateGraph --> Done +``` + +## Caching and Performance + +### Nx Caching + +Nx automatically caches build outputs and only rebuilds what changed: + +```bash +# First run builds everything +npx nx build cli + +# Second run uses cache (much faster) +npx nx build cli +``` + +### Clearing Cache + +```bash +# Clear all caches +npx nx reset +``` + +## Additional Resources + +- [Nx Documentation](https://nx.dev/getting-started/intro) +- [Nx CLI Reference](https://nx.dev/packages/nx/documents/cli) +- [Nx Workspace Configuration](https://nx.dev/reference/project-configuration) diff --git a/package-lock.json b/package-lock.json index d5c0472d387..77067a48039 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "@nx/eslint": "21.3.11", "@nx/jest": "21.3.11", "@nx/js": "21.3.11", + "@nx/webpack": "21.3.11", "big-integer": "1.6.52", "braintree-web-drop-in": "1.44.0", "buffer": "6.0.3", @@ -4808,6 +4809,12 @@ "integrity": "sha512-UIrJB+AfKU0CCfbMoWrsGpd2D/hBpY/SGgFI6WRHPOwhaZ3g9rz1weiJ6eb6L9KgVyunT7s2tckcPkbHw+NzeA==", "license": "MIT" }, + "node_modules/@bufbuild/protobuf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz", + "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@compodoc/compodoc": { "version": "1.1.26", "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.26.tgz", @@ -7734,7 +7741,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "devOptional": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -7879,7 +7885,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -7916,7 +7921,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -7943,7 +7947,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -7960,7 +7963,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.1", @@ -7983,7 +7985,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -8028,7 +8029,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true, "license": "MIT" }, "node_modules/@listr2/prompt-adapter-inquirer": { @@ -8840,7 +8840,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -8854,7 +8853,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -8864,7 +8862,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -9367,19 +9364,6 @@ } } }, - "node_modules/@nx/eslint/node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@nx/jest": { "version": "21.3.11", "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-21.3.11.tgz", @@ -10714,6 +10698,517 @@ "win32" ] }, + "node_modules/@nx/webpack": { + "version": "21.3.11", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-21.3.11.tgz", + "integrity": "sha512-GAqA9yHLro4zDf2z27uWseUSLiZZh2IZ3Eh5Kb9l/LA4ujT3whkpNoIo/K2LxzmmOG8k2SkJ7wBntCPk2O1e8g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.2", + "@nx/devkit": "21.3.11", + "@nx/js": "21.3.11", + "@phenomnomnominal/tsquery": "~5.0.1", + "ajv": "^8.12.0", + "autoprefixer": "^10.4.9", + "babel-loader": "^9.1.2", + "browserslist": "^4.21.4", + "copy-webpack-plugin": "^10.2.4", + "css-loader": "^6.4.0", + "css-minimizer-webpack-plugin": "^5.0.0", + "fork-ts-checker-webpack-plugin": "7.2.13", + "less": "^4.1.3", + "less-loader": "^11.1.0", + "license-webpack-plugin": "^4.0.2", + "loader-utils": "^2.0.3", + "mini-css-extract-plugin": "~2.4.7", + "parse5": "4.0.0", + "picocolors": "^1.1.0", + "postcss": "^8.4.38", + "postcss-import": "~14.1.0", + "postcss-loader": "^6.1.1", + "rxjs": "^7.8.0", + "sass": "^1.85.0", + "sass-embedded": "^1.83.4", + "sass-loader": "^16.0.4", + "source-map-loader": "^5.0.0", + "style-loader": "^3.3.0", + "terser-webpack-plugin": "^5.3.3", + "ts-loader": "^9.3.1", + "tsconfig-paths-webpack-plugin": "4.0.0", + "tslib": "^2.3.0", + "webpack": "~5.99.0", + "webpack-dev-server": "^5.2.1", + "webpack-node-externals": "^3.0.0", + "webpack-subresource-integrity": "^5.1.0" + } + }, + "node_modules/@nx/webpack/node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nx/webpack/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@nx/webpack/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@nx/webpack/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@nx/webpack/node_modules/copy-webpack-plugin": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", + "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^12.0.2", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 12.20.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/@nx/webpack/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/webpack/node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@nx/webpack/node_modules/fork-ts-checker-webpack-plugin": { + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz", + "integrity": "sha512-fR3WRkOb4bQdWB/y7ssDUlVdrclvwtyCUIHCfivAoYxq9dF7XfrDKbMdZIfwJ7hxIAqkYSGeU7lLJE6xrxIBdg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "vue-template-compiler": "*", + "webpack": "^5.11.0" + }, + "peerDependenciesMeta": { + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/@nx/webpack/node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nx/webpack/node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@nx/webpack/node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@nx/webpack/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nx/webpack/node_modules/globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "license": "MIT", + "dependencies": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nx/webpack/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@nx/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/@nx/webpack/node_modules/less-loader": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.4.tgz", + "integrity": "sha512-6/GrYaB6QcW6Vj+/9ZPgKKs6G10YZai/l/eJ4SLwbzqNTBsAqt5hSLVF47TgsiBxV1P6eAU0GYRH3YRuQU9V3A==", + "license": "MIT", + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/@nx/webpack/node_modules/mini-css-extract-plugin": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.7.tgz", + "integrity": "sha512-euWmddf0sk9Nv1O0gfeeUAvAkoSlWncNLF77C0TP2+WoPvy8mAHKOzMajcCz2dzvyt3CNgxb1obIEVFIRxaipg==", + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@nx/webpack/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nx/webpack/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nx/webpack/node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/@nx/webpack/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nx/webpack/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@nx/webpack/node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@nx/webpack/node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-fw/7265mIWukrSHd0i+wSwx64kYUSAKPfxRDksjKIYTxSAp9W9/xcZVBF4Kl0eqQd5eBpAQ/oQrc5RyM/0c1GQ==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@nx/webpack/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/@nx/workspace": { "version": "21.3.11", "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.3.11.tgz", @@ -10734,7 +11229,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -10774,7 +11268,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10795,7 +11288,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10816,7 +11308,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10837,7 +11328,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10858,7 +11348,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10879,7 +11368,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10900,7 +11388,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10921,7 +11408,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10942,7 +11428,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10963,7 +11448,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -10984,7 +11468,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11005,7 +11488,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11026,7 +11508,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -11044,7 +11525,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, "license": "Apache-2.0", "optional": true, "bin": { @@ -11058,7 +11538,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -11896,7 +12375,6 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "devOptional": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -13183,6 +13661,15 @@ "node": ">= 10" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@ts-morph/common": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz", @@ -13346,7 +13833,6 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -13357,7 +13843,6 @@ "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -13391,7 +13876,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -13401,7 +13885,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", @@ -13442,7 +13925,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", @@ -13453,7 +13935,6 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, "license": "MIT", "dependencies": { "@types/eslint": "*", @@ -13470,7 +13951,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -13482,7 +13962,6 @@ "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -13546,7 +14025,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/http-assert": { @@ -13567,14 +14046,12 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, "license": "MIT" }, "node_modules/@types/http-proxy": { "version": "1.17.16", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -13822,7 +14299,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/ms": { @@ -13895,7 +14371,6 @@ "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -13950,14 +14425,12 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { @@ -14009,7 +14482,6 @@ "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -14020,7 +14492,6 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, "license": "MIT", "dependencies": { "@types/express": "*" @@ -14030,7 +14501,6 @@ "version": "1.15.8", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -14042,7 +14512,6 @@ "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -14120,7 +14589,6 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -15049,7 +15517,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", @@ -15060,28 +15527,24 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", @@ -15093,14 +15556,12 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -15113,7 +15574,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" @@ -15123,7 +15583,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" @@ -15133,14 +15592,12 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -15157,7 +15614,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -15171,7 +15627,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -15184,7 +15639,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -15199,7 +15653,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -15274,14 +15727,12 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, "license": "Apache-2.0" }, "node_modules/@yao-pkg/pkg": { @@ -15686,7 +16137,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -15721,7 +16171,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" @@ -15793,7 +16242,6 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, "engines": [ "node >= 0.8.0" ], @@ -16111,7 +16559,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, "license": "MIT" }, "node_modules/array-includes": { @@ -16329,7 +16776,6 @@ "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -16367,7 +16813,6 @@ "version": "4.25.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -16502,7 +16947,6 @@ "version": "9.2.1", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "dev": true, "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", @@ -16807,7 +17251,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true, "license": "MIT" }, "node_modules/bcryptjs": { @@ -16932,7 +17375,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -16942,7 +17384,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17031,7 +17472,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -17042,7 +17482,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, "license": "ISC" }, "node_modules/boolean": { @@ -17218,6 +17657,12 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "license": "MIT/X11" + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -17709,7 +18154,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", @@ -17735,6 +18180,18 @@ "node": ">= 6" } }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001724", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", @@ -17931,7 +18388,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -17981,7 +18437,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0" @@ -17998,7 +18453,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "devOptional": true, "funding": [ { "type": "github", @@ -18021,7 +18475,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "source-map": "~0.6.0" @@ -18034,7 +18488,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -18320,11 +18774,22 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, + "license": "MIT" + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", "license": "MIT" }, "node_modules/colors": { @@ -18375,7 +18840,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true, "license": "ISC" }, "node_modules/common-tags": { @@ -18409,7 +18873,6 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -18422,7 +18885,6 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", - "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -18441,7 +18903,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -18451,14 +18912,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/compression/node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -18692,7 +19151,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -18784,7 +19242,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "dev": true, "license": "MIT", "dependencies": { "is-what": "^3.14.1" @@ -19056,6 +19513,18 @@ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", "license": "MIT" }, + "node_modules/css-declaration-sorter": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", + "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, "node_modules/css-loader": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", @@ -19092,11 +19561,54 @@ } } }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -19109,11 +19621,23 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -19133,7 +19657,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -19142,6 +19665,115 @@ "node": ">=4" } }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -19590,7 +20222,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, "license": "MIT" }, "node_modules/detect-port": { @@ -19798,7 +20429,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "license": "MIT", "dependencies": { "path-type": "^4.0.0" @@ -19811,7 +20441,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -19927,7 +20556,6 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dev": true, "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" @@ -19960,7 +20588,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "utila": "~0.4" @@ -19970,7 +20598,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", @@ -19985,7 +20612,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, "funding": [ { "type": "github", @@ -20012,7 +20638,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" @@ -20028,7 +20653,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -20050,7 +20674,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -20517,7 +21141,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -20555,7 +21178,6 @@ "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -20649,7 +21271,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -20759,7 +21380,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -21514,14 +22134,12 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, "license": "MIT" }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -21835,7 +22453,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -21852,7 +22469,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -21877,7 +22493,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, "funding": [ { "type": "github", @@ -21904,7 +22519,6 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -21914,7 +22528,6 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" @@ -22118,7 +22731,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dev": true, "license": "MIT", "dependencies": { "common-path-prefix": "^3.0.0", @@ -22557,7 +23169,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -22707,7 +23318,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "dev": true, "license": "Unlicense" }, "node_modules/fs.realpath": { @@ -22941,7 +23551,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/minimatch": { @@ -23154,7 +23763,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true, "license": "MIT" }, "node_modules/handlebars": { @@ -23316,7 +23924,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "he": "bin/he" @@ -23372,7 +23980,6 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.1", @@ -23480,7 +24087,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -23513,7 +24120,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 12" @@ -23523,7 +24130,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "camel-case": "^4.1.2", @@ -23668,7 +24275,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true, "license": "MIT" }, "node_modules/http-errors": { @@ -23700,14 +24306,12 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "dev": true, "license": "MIT" }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", @@ -23816,7 +24420,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.18" @@ -23888,7 +24491,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" @@ -23956,7 +24558,6 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "dev": true, "license": "MIT", "optional": true, "bin": { @@ -23976,7 +24577,6 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", - "dev": true, "license": "MIT" }, "node_modules/import-fresh": { @@ -24348,7 +24948,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -24612,7 +25211,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", - "dev": true, "license": "MIT", "engines": { "node": ">=16" @@ -24872,7 +25470,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true, "license": "MIT" }, "node_modules/is-windows": { @@ -26572,7 +27169,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -26590,7 +27186,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -26806,7 +27401,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "devOptional": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -26822,7 +27416,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -27010,7 +27603,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { @@ -27195,6 +27787,15 @@ "node": ">=6" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/koa": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", @@ -27454,7 +28055,6 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", - "dev": true, "license": "MIT", "dependencies": { "picocolors": "^1.0.0", @@ -27472,7 +28072,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", "integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "copy-anything": "^2.0.1", @@ -27526,7 +28125,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -27541,7 +28139,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, "license": "MIT", "optional": true, "bin": { @@ -27555,7 +28152,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -27566,7 +28162,6 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, "license": "ISC", "optional": true, "bin": { @@ -27577,7 +28172,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "optional": true, "engines": { @@ -27610,7 +28204,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", - "dev": true, "license": "ISC", "dependencies": { "webpack-sources": "^3.0.0" @@ -27637,7 +28230,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -28090,7 +28682,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" @@ -28159,7 +28750,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { @@ -28168,6 +28758,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -28459,7 +29055,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -28937,6 +29533,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -28950,7 +29552,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, "license": "Unlicense", "dependencies": { "fs-monkey": "^1.0.4" @@ -28991,7 +29592,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -29001,7 +29601,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -29725,7 +30324,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, "license": "ISC" }, "node_modules/minimatch": { @@ -30169,7 +30767,6 @@ "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", @@ -30268,7 +30865,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -30315,7 +30911,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -30342,7 +30937,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, "license": "MIT" }, "node_modules/neotraverse": { @@ -30373,7 +30967,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "lower-case": "^2.0.2", @@ -30397,7 +30991,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true, "license": "MIT" }, "node_modules/node-api-version": { @@ -30935,7 +31528,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -31411,7 +32003,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" @@ -32141,7 +32732,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true, "license": "MIT" }, "node_modules/oidc-client-ts": { @@ -32173,7 +32763,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -32417,7 +33006,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/retry": "0.12.2", @@ -32435,14 +33023,12 @@ "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true, "license": "MIT" }, "node_modules/p-retry/node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -32796,7 +33382,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -32849,7 +33435,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -32932,7 +33517,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -33145,7 +33730,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dev": true, "license": "MIT", "dependencies": { "find-up": "^6.3.0" @@ -33161,7 +33745,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^7.1.0", @@ -33178,7 +33761,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^6.0.0" @@ -33194,7 +33776,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" @@ -33210,7 +33791,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^4.0.0" @@ -33226,7 +33806,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -33236,7 +33815,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.20" @@ -33413,7 +33991,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -33438,6 +34015,117 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-calc/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/postcss-import": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", @@ -33551,11 +34239,134 @@ "dev": true, "license": "MIT" }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" @@ -33568,7 +34379,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", @@ -33586,7 +34396,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "dev": true, "license": "ISC", "dependencies": { "postcss-selector-parser": "^7.0.0" @@ -33602,7 +34411,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" @@ -33654,11 +34462,234 @@ "node": ">=4" } }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/postcss-selector-parser": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -33672,7 +34703,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, "license": "MIT" }, "node_modules/postject": { @@ -33840,7 +34870,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "lodash": "^4.17.20", @@ -34035,7 +35065,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true, "license": "MIT", "optional": true }, @@ -34128,7 +35157,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -34162,7 +35190,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -34276,7 +35303,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -34286,7 +35312,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -34323,7 +35348,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -34520,7 +35544,7 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.10" @@ -34595,7 +35619,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "css-select": "^4.1.3", @@ -34609,7 +35633,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -34626,7 +35650,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", @@ -34641,7 +35665,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.2.0" @@ -34657,7 +35681,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^1.0.1", @@ -34672,7 +35696,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -34682,7 +35706,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, + "devOptional": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -34711,7 +35735,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -34942,7 +35965,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -35094,7 +36116,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -35282,7 +36303,6 @@ "version": "1.88.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz", "integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==", - "dev": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -35299,11 +36319,397 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/sass-embedded": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz", + "integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.93.2", + "sass-embedded-android-arm": "1.93.2", + "sass-embedded-android-arm64": "1.93.2", + "sass-embedded-android-riscv64": "1.93.2", + "sass-embedded-android-x64": "1.93.2", + "sass-embedded-darwin-arm64": "1.93.2", + "sass-embedded-darwin-x64": "1.93.2", + "sass-embedded-linux-arm": "1.93.2", + "sass-embedded-linux-arm64": "1.93.2", + "sass-embedded-linux-musl-arm": "1.93.2", + "sass-embedded-linux-musl-arm64": "1.93.2", + "sass-embedded-linux-musl-riscv64": "1.93.2", + "sass-embedded-linux-musl-x64": "1.93.2", + "sass-embedded-linux-riscv64": "1.93.2", + "sass-embedded-linux-x64": "1.93.2", + "sass-embedded-unknown-all": "1.93.2", + "sass-embedded-win32-arm64": "1.93.2", + "sass-embedded-win32-x64": "1.93.2" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.93.2.tgz", + "integrity": "sha512-GdEuPXIzmhRS5J7UKAwEvtk8YyHQuFZRcpnEnkA3rwRUI27kwjyXkNeIj38XjUQ3DzrfMe8HcKFaqWGHvblS7Q==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "sass": "1.93.2" + } + }, + "node_modules/sass-embedded-all-unknown/node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.93.2.tgz", + "integrity": "sha512-I8bpO8meZNo5FvFx5FIiE7DGPVOYft0WjuwcCCdeJ6duwfkl6tZdatex1GrSigvTsuz9L0m4ngDcX/Tj/8yMow==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.93.2.tgz", + "integrity": "sha512-346f4iVGAPGcNP6V6IOOFkN5qnArAoXNTPr5eA/rmNpeGwomdb7kJyQ717r9rbJXxOG8OAAUado6J0qLsjnjXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.93.2.tgz", + "integrity": "sha512-hSMW1s4yJf5guT9mrdkumluqrwh7BjbZ4MbBW9tmi1DRDdlw1Wh9Oy1HnnmOG8x9XcI1qkojtPL6LUuEJmsiDg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.93.2.tgz", + "integrity": "sha512-JqktiHZduvn+ldGBosE40ALgQ//tGCVNAObgcQ6UIZznEJbsHegqStqhRo8UW3x2cgOO2XYJcrInH6cc7wdKbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.93.2.tgz", + "integrity": "sha512-qI1X16qKNeBJp+M/5BNW7v/JHCDYWr1/mdoJ7+UMHmP0b5AVudIZtimtK0hnjrLnBECURifd6IkulybR+h+4UA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.93.2.tgz", + "integrity": "sha512-4KeAvlkQ0m0enKUnDGQJZwpovYw99iiMb8CTZRSsQm8Eh7halbJZVmx67f4heFY/zISgVOCcxNg19GrM5NTwtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.93.2.tgz", + "integrity": "sha512-N3+D/ToHtzwLDO+lSH05Wo6/KRxFBPnbjVHASOlHzqJnK+g5cqex7IFAp6ozzlRStySk61Rp6d+YGrqZ6/P0PA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.93.2.tgz", + "integrity": "sha512-9ftX6nd5CsShJqJ2WRg+ptaYvUW+spqZfJ88FbcKQBNFQm6L87luj3UI1rB6cP5EWrLwHA754OKxRJyzWiaN6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.93.2.tgz", + "integrity": "sha512-XBTvx66yRenvEsp3VaJCb3HQSyqCsUh7R+pbxcN5TuzueybZi0LXvn9zneksdXcmjACMlMpIVXi6LyHPQkYc8A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.93.2.tgz", + "integrity": "sha512-+3EHuDPkMiAX5kytsjEC1bKZCawB9J6pm2eBIzzLMPWbf5xdx++vO1DpT7hD4bm4ZGn0eVHgSOKIfP6CVz6tVg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.93.2.tgz", + "integrity": "sha512-0sB5kmVZDKTYzmCSlTUnjh6mzOhzmQiW/NNI5g8JS4JiHw2sDNTvt1dsFTuqFkUHyEOY3ESTsfHHBQV8Ip4bEA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.93.2.tgz", + "integrity": "sha512-t3ejQ+1LEVuHy7JHBI2tWHhoMfhedUNDjGJR2FKaLgrtJntGnyD1RyX0xb3nuqL/UXiEAtmTmZY+Uh3SLUe1Hg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.93.2.tgz", + "integrity": "sha512-e7AndEwAbFtXaLy6on4BfNGTr3wtGZQmypUgYpSNVcYDO+CWxatKVY4cxbehMPhxG9g5ru+eaMfynvhZt7fLaA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.93.2.tgz", + "integrity": "sha512-U3EIUZQL11DU0xDDHXexd4PYPHQaSQa2hzc4EzmhHqrAj+TyfYO94htjWOd+DdTPtSwmLp+9cTWwPZBODzC96w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.93.2.tgz", + "integrity": "sha512-7VnaOmyewcXohiuoFagJ3SK5ddP9yXpU0rzz+pZQmS1/+5O6vzyFCUoEt3HDRaLctH4GT3nUGoK1jg0ae62IfQ==", + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "dependencies": { + "sass": "1.93.2" + } + }, + "node_modules/sass-embedded-unknown-all/node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.93.2.tgz", + "integrity": "sha512-Y90DZDbQvtv4Bt0GTXKlcT9pn4pz8AObEjFF8eyul+/boXwyptPZ/A1EyziAeNaIEIfxyy87z78PUgCeGHsx3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz", + "integrity": "sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/sass-loader": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz", "integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==", - "dev": true, "license": "MIT", "dependencies": { "neo-async": "^2.6.2" @@ -35344,7 +36750,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/saxes": { @@ -35373,7 +36779,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -35393,7 +36798,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -35411,14 +36815,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true, "license": "MIT" }, "node_modules/selfsigned": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/node-forge": "^1.3.0", @@ -35505,7 +36907,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -35515,7 +36916,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.4", @@ -35534,7 +36934,6 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -35548,7 +36947,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -35558,7 +36956,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -35568,7 +36965,6 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, "license": "MIT", "dependencies": { "depd": "~1.1.2", @@ -35584,14 +36980,12 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, "license": "ISC" }, "node_modules/serve-index/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -35601,7 +36995,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -35614,14 +37007,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/serve-index/node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -35631,14 +37022,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true, "license": "ISC" }, "node_modules/serve-index/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -35771,7 +37160,6 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -35996,7 +37384,6 @@ "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", @@ -36008,7 +37395,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "license": "MIT", "bin": { "uuid": "dist/bin/uuid" @@ -36061,7 +37447,6 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 8" @@ -36071,7 +37456,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -36081,7 +37465,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", - "dev": true, "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", @@ -36102,7 +37485,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -36113,7 +37495,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -36317,7 +37698,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -36334,7 +37714,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -36349,7 +37728,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -36761,6 +38139,35 @@ "webpack": "^5.27.0" } }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/stylehacks/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -36928,6 +38335,40 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -36944,6 +38385,27 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/synckit": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", @@ -37096,7 +38558,6 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -37320,7 +38781,6 @@ "version": "5.39.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -37339,7 +38799,6 @@ "version": "5.3.14", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -37374,7 +38833,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -37389,7 +38847,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -37405,7 +38862,6 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, "license": "MIT" }, "node_modules/test-exclude": { @@ -37492,7 +38948,6 @@ "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", - "dev": true, "license": "Unlicense", "engines": { "node": ">=10.18" @@ -37511,7 +38966,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true, "license": "MIT" }, "node_modules/tiny-async-pool": { @@ -37688,7 +39142,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -37843,7 +39296,6 @@ "version": "9.5.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -38522,7 +39974,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", - "dev": true, "license": "MIT" }, "node_modules/typedarray": { @@ -38545,7 +39996,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -39108,14 +40558,13 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -39182,6 +40631,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -39827,7 +41282,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -39841,7 +41295,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" @@ -39877,7 +41330,6 @@ "version": "5.99.7", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz", "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", - "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -39978,7 +41430,6 @@ "version": "7.4.2", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", - "dev": true, "license": "MIT", "dependencies": { "colorette": "^2.0.10", @@ -40008,7 +41459,6 @@ "version": "4.17.2", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/json-pack": "^1.0.3", @@ -40028,7 +41478,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -40038,7 +41487,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -40051,7 +41499,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz", "integrity": "sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", @@ -40109,7 +41556,6 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -40122,7 +41568,6 @@ "version": "4.19.6", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -40135,7 +41580,6 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -40149,7 +41593,6 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -40174,7 +41617,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -40199,7 +41641,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -40212,7 +41653,6 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -40222,14 +41662,12 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, "license": "MIT" }, "node_modules/webpack-dev-server/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -40239,14 +41677,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, "license": "MIT" }, "node_modules/webpack-dev-server/node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -40293,7 +41729,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -40312,7 +41747,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -40322,7 +41756,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -40335,7 +41768,6 @@ "version": "2.0.9", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", @@ -40360,7 +41792,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -40373,7 +41804,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -40383,7 +41813,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -40396,7 +41825,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -40406,7 +41834,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -40416,7 +41843,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, "license": "MIT", "bin": { "mime": "cli.js" @@ -40429,7 +41855,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -40439,7 +41864,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -40452,7 +41876,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -40462,14 +41885,12 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, "license": "MIT" }, "node_modules/webpack-dev-server/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -40482,7 +41903,6 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -40498,7 +41918,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -40514,7 +41933,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -40527,7 +41945,6 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -40552,7 +41969,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -40562,7 +41978,6 @@ "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", @@ -40578,7 +41993,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -40588,7 +42002,6 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -40629,7 +42042,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -40639,7 +42051,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -40649,7 +42060,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", - "dev": true, "license": "MIT", "dependencies": { "typed-assert": "^1.0.8" @@ -40678,7 +42088,6 @@ "version": "4.25.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -40711,7 +42120,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -40725,7 +42133,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -40735,14 +42142,12 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/webpack/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -40752,7 +42157,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -40765,7 +42169,6 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -40780,7 +42183,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=0.8.0" diff --git a/package.json b/package.json index 97c6cc0eeae..aec6ddb9536 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "@nx/eslint": "21.3.11", "@nx/jest": "21.3.11", "@nx/js": "21.3.11", + "@nx/webpack": "21.3.11", "big-integer": "1.6.52", "braintree-web-drop-in": "1.44.0", "buffer": "6.0.3", From c0d15c19d4f3a99a2866c1bfff12e3dff110a83f Mon Sep 17 00:00:00 2001 From: Brandon Treston <btreston@bitwarden.com> Date: Tue, 7 Oct 2025 09:59:57 -0400 Subject: [PATCH 80/83] [PM-26366] Refactor BasePolicyEditDefinition (#16756) * refactor BasePoliicyEditDefinition * fix circular dep --- .../organizations/policies/base-policy-edit.component.ts | 9 +++++++++ .../organizations/policies/policies.component.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts index 2e5faea4702..9293c686b7f 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts @@ -9,6 +9,8 @@ import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/po import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import type { PolicyEditDialogComponent } from "./policy-edit-dialog.component"; + /** * A metadata class that defines how a policy is displayed in the Admin Console Policies page for editing. * Add this to the `ossPolicyRegister` or `bitPolicyRegister` file to register it in the application. @@ -32,6 +34,13 @@ export abstract class BasePolicyEditDefinition { */ abstract component: Constructor<BasePolicyEditComponent>; + /** + * The dialog component that will be opened when editing this policy. + * This allows customizing the look and feel of each policy's dialog contents. + * If not specified, defaults to {@link PolicyEditDialogComponent}. + */ + editDialogComponent?: typeof PolicyEditDialogComponent; + /** * If true, the {@link description} will be reused in the policy edit modal. Set this to false if you * have more complex requirements that you will implement in your template instead. diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index e2c51b77d45..95c00f74f1c 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -127,7 +127,8 @@ export class PoliciesComponent implements OnInit { } async edit(policy: BasePolicyEditDefinition) { - const dialogRef = PolicyEditDialogComponent.open(this.dialogService, { + const dialogComponent = policy.editDialogComponent ?? PolicyEditDialogComponent; + const dialogRef = dialogComponent.open(this.dialogService, { data: { policy: policy, organizationId: this.organizationId, From 801700d441b6b54d779a82a5accc68f7e46a79c9 Mon Sep 17 00:00:00 2001 From: Graham Walker <ghwtx@icloud.com> Date: Tue, 7 Oct 2025 09:37:59 -0500 Subject: [PATCH 81/83] PM-26015 Datadog integration card (#16559) * PM-26015 adding Datadog integration card * PM-26015 removing 2 changes * PM-26015 Removing 1 change * PM-26015 adding datadog integration card * PM-26015 fixing code to accept new toast owner changes * PM-26015 fixing linting error * PM-26015 fixing pr comment --- .../browser-system-notification.service.ts | 4 +- .../integrations/logo-datadog-color.svg | 1 + apps/web/src/locales/en/messages.json | 3 + .../configuration/datadog-configuration.ts | 17 + .../datadog-template.ts | 17 + .../models/integration.ts | 2 + .../organization-integration-configuration.ts | 5 +- .../organization-integration-service-type.ts | 1 + .../models/organization-integration-type.ts | 1 + .../models/organization-integration.ts | 5 +- ...g-organization-integration-service.spec.ts | 184 +++++++++ ...atadog-organization-integration-service.ts | 350 ++++++++++++++++++ .../hec-organization-integration-service.ts | 30 +- .../integration-card.component.spec.ts | 3 + .../integration-card.component.ts | 172 +++++++-- .../connect-dialog-datadog.component.html | 58 +++ .../connect-dialog-datadog.component.spec.ts | 171 +++++++++ .../connect-dialog-datadog.component.ts | 121 ++++++ .../integration-dialog/index.ts | 1 + .../integration-grid.component.spec.ts | 3 + .../integrations.component.ts | 56 +++ .../organization-integrations.module.ts | 6 + .../integrations.component.spec.ts | 3 + 23 files changed, 1165 insertions(+), 49 deletions(-) create mode 100644 apps/web/src/images/integrations/logo-datadog-color.svg create mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/datadog-configuration.ts create mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts create mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.spec.ts create mode 100644 bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.ts create mode 100644 bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.html create mode 100644 bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.spec.ts create mode 100644 bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.ts diff --git a/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts b/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts index b835c711853..7dcd8a12392 100644 --- a/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts +++ b/apps/browser/src/platform/system-notifications/browser-system-notification.service.ts @@ -70,8 +70,8 @@ export class BrowserSystemNotificationService implements SystemNotificationsServ } async clear(clearInfo: SystemNotificationClearInfo): Promise<undefined> { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - chrome.notifications.clear(clearInfo.id); + await chrome.notifications.clear(clearInfo.id); + return undefined; } isSupported(): boolean { diff --git a/apps/web/src/images/integrations/logo-datadog-color.svg b/apps/web/src/images/integrations/logo-datadog-color.svg new file mode 100644 index 00000000000..62e608cd544 --- /dev/null +++ b/apps/web/src/images/integrations/logo-datadog-color.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="114" height="113" fill="none"><g clip-path="url(#a)"><mask id="b" width="115" height="122" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:luminance"><path fill="#fff" d="M114.686 0H.45v121.828h114.236V0Z"/></mask><g mask="url(#b)"><path fill="#5C2D90" d="m102.158 59.532 2.781 35.489-49.225 8.833-4.238-35.489 7.033-.982c1.144.491 1.963.654 3.271.982 2.126.49 4.58 1.144 8.175-.817.818-.491 2.617-1.964 3.272-2.944l28.931-5.072Zm-76.05 42.198c-.818-1.307-2.29-2.78-4.58-4.58-3.272-2.617-2.126-7.197-.165-9.976 2.472-4.742 15.374-10.957 14.72-18.645-.326-2.78-.653-6.379-3.271-8.996-.164.982 0 1.964 0 1.964s-1.144-1.307-1.635-3.272c-.491-.653-.982-.982-1.473-1.963-.326 1.144-.326 2.29-.326 2.29s-.818-2.126-.982-3.761c-.49.817-.653 2.29-.653 2.29s-1.144-3.272-.818-5.068c-.49-1.472-2.126-4.58-1.635-11.448 2.781 1.964 8.996 1.473 11.449-2.126.817-1.144 1.306-4.414-.327-10.795-1.144-4.089-3.925-10.14-5.067-12.43l-.165.165c.654 1.8 1.801 5.724 2.29 7.687 1.307 5.724 1.801 7.688 1.145 10.304-.491 2.29-1.802 3.761-5.234 5.398-3.271 1.635-7.85-2.472-8.016-2.617-3.271-2.617-5.724-6.886-6.05-8.832-.327-2.29 1.306-3.598 2.125-5.397-1.144.327-2.472.982-2.472.982s1.473-1.635 3.434-2.944a16.649 16.649 0 0 0 2.126-1.472h-2.104s1.963-1.144 4.089-1.801H19.57l7.85-3.434c2.472-.982 4.743-.653 6.051 1.144 1.801 2.472 3.599 3.76 7.36 4.742 2.29-.981 3.107-1.635 6.05-2.471 2.617-2.944 4.742-3.272 4.742-3.272s-1.472 1.306-1.635 2.78c1.473-1.143 3.108-2.125 3.108-2.125s-.653.817-1.144 1.963l.164.164c1.801-.981 3.76-1.8 3.76-1.8s-.652.653-1.306 1.634c1.307 0 3.925 0 4.909.165 6.05.164 7.359-6.38 9.649-7.36 2.943-.981 4.251-1.634 9.158 3.272 4.252 4.252 7.522 11.613 5.889 13.248-1.307 1.306-4.09-.491-7.197-4.252-1.635-1.963-2.781-4.414-3.434-7.359-.327-2.472-2.126-3.925-2.126-3.925s1.144 2.472 1.144 4.58c0 1.144.164 5.398 1.963 7.85-.164.327-.326 1.8-.49 1.963-2.126-2.616-6.886-4.414-7.522-5.067 2.617 2.126 8.505 6.886 10.795 11.448 2.126 4.414.817 8.341 1.963 9.323.327.326 4.58 5.561 5.398 8.34 1.472 4.743.164 9.65-1.801 12.593l-5.233.818a6.922 6.922 0 0 1-1.964-.656c.327-.653 1.144-2.29 1.144-2.616l-.326-.491c-1.635 2.29-4.414 4.58-6.706 5.888-2.943 1.635-6.38 1.473-8.652.654-6.379-1.964-12.43-6.215-13.736-7.36 0 0 0 .818.164 1.145a49.564 49.564 0 0 0 8.832 7.359l-7.522.817 3.599 27.804c-1.636.164-1.802.326-3.599.653-1.472-5.398-4.414-8.832-7.687-10.958-2.781-1.8-6.706-2.29-10.467-1.472l-.164.327c2.616-.327 5.724.164 8.831 2.125 3.108 1.962 5.562 7.033 6.542 9.976 1.144 3.925 1.963 8.016-1.144 12.43-2.29 3.108-8.832 4.909-14.065 1.144 1.472 2.29 3.272 4.089 5.888 4.414 3.761.491 7.36-.164 9.976-2.616 2.126-2.126 3.272-6.706 2.943-11.613l3.434-.49 1.145 8.651 55.746-6.653-4.909-44.485-2.781.49L96.598 0 3.703 10.795l11.448 92.568 10.958-1.633Z"/></g><path fill="#5C2D90" d="M70.307 53.075c2.471 1.8 4.58 2.943 6.706 2.78 1.306-.164 2.616-2.29 3.434-4.251.653-1.307.653-2.78-.327-3.272-.49-.164-2.472-.164-3.925 0-2.78.327-5.562 1.307-6.215 1.801-.982.653-.49 2.29.327 2.944m.653-19.14v.164l.164.327c.653 1.306 1.307 2.617 2.617 3.272.326 0 .653-.165.981-.165 1.145 0 1.801.165 2.29.327v-.982c-.164-1.635.327-4.58-2.943-6.215-1.306-.653-2.943-.326-3.598.327h.326c.818.327.327.653.165.982-.165.653-.329.817-.002 1.963ZM56.24 34.265c.817-.653-3.76-1.635-7.36.654-2.616 1.8-2.616 5.561-.163 7.687.245.122.467.289.653.491.653-.327 1.635-.653 2.472-.982 1.635-.49 2.943-.817 4.089-.981.49-.654 1.144-1.635.982-3.434-.165-2.472-2.126-1.964-.654-3.435m26.658 39.727-7.85 13.066-9.159-2.781-8.016 12.266.327 3.924 43.832-8.015-2.617-27.315-7.197 15.047-9.32-6.192Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M.273 0h113v113h-113z"/></clipPath></defs></svg> \ No newline at end of file diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index cda4e70f915..c104e9776c1 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9994,6 +9994,9 @@ "crowdstrikeEventIntegrationDesc": { "message": "Send event data to your Logscale instance" }, + "datadogEventIntegrationDesc": { + "message": "Send vault event data to your Datadog instance" + }, "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/datadog-configuration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/datadog-configuration.ts new file mode 100644 index 00000000000..e788ebba7f2 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/configuration/datadog-configuration.ts @@ -0,0 +1,17 @@ +import { OrganizationIntegrationServiceType } from "../organization-integration-service-type"; + +export class DatadogConfiguration { + uri: string; + apiKey: string; + service: OrganizationIntegrationServiceType; + + constructor(uri: string, apiKey: string, service: string) { + this.uri = uri; + this.apiKey = apiKey; + this.service = service as OrganizationIntegrationServiceType; + } + + toString(): string { + return JSON.stringify(this); + } +} diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts new file mode 100644 index 00000000000..9aa6e34f478 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts @@ -0,0 +1,17 @@ +import { OrganizationIntegrationServiceType } from "../../organization-integration-service-type"; + +export class DatadogTemplate { + source_type_name = "Bitwarden"; + title: string = "#Title#"; + text: string = + "ActingUser: #ActingUserId#\nUser: #UserId#\nEvent: #Type#\nOrganization: #OrganizationId#\nPolicyId: #PolicyId#\nIpAddress: #IpAddress#\nDomainName: #DomainName#\nCipherId: #CipherId#\n"; + service: OrganizationIntegrationServiceType; + + constructor(service: string) { + this.service = service as OrganizationIntegrationServiceType; + } + + toString(): string { + return JSON.stringify(this); + } +} diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts index abd1861caa9..2167694b720 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts @@ -1,6 +1,7 @@ import { IntegrationType } from "@bitwarden/common/enums/integration-type.enum"; import { OrganizationIntegration } from "./organization-integration"; +import { OrganizationIntegrationType } from "./organization-integration-type"; /** Integration or SDK */ export type Integration = { @@ -23,6 +24,7 @@ export type Integration = { canSetupConnection?: boolean; configuration?: string; template?: string; + integrationType?: OrganizationIntegrationType | null; // OrganizationIntegration organizationIntegration?: OrganizationIntegration | null; diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-configuration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-configuration.ts index d4bbd30055f..0209460b630 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-configuration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-configuration.ts @@ -4,6 +4,7 @@ import { OrganizationIntegrationId, } from "@bitwarden/common/types/guid"; +import { DatadogTemplate } from "./integration-configuration-config/configuration-template/datadog-template"; import { HecTemplate } from "./integration-configuration-config/configuration-template/hec-template"; import { WebhookTemplate } from "./integration-configuration-config/configuration-template/webhook-template"; import { WebhookIntegrationConfigurationConfig } from "./integration-configuration-config/webhook-integration-configuration-config"; @@ -14,7 +15,7 @@ export class OrganizationIntegrationConfiguration { eventType?: EventType | null; configuration?: WebhookIntegrationConfigurationConfig | null; filters?: string; - template?: HecTemplate | WebhookTemplate | null; + template?: HecTemplate | WebhookTemplate | DatadogTemplate | null; constructor( id: OrganizationIntegrationConfigurationId, @@ -22,7 +23,7 @@ export class OrganizationIntegrationConfiguration { eventType?: EventType | null, configuration?: WebhookIntegrationConfigurationConfig | null, filters?: string, - template?: HecTemplate | WebhookTemplate | null, + template?: HecTemplate | WebhookTemplate | DatadogTemplate | null, ) { this.id = id; this.integrationId = integrationId; diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-service-type.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-service-type.ts index dd1b4fb3f6c..e9e93adc0ff 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-service-type.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-service-type.ts @@ -1,5 +1,6 @@ export const OrganizationIntegrationServiceType = Object.freeze({ CrowdStrike: "CrowdStrike", + Datadog: "Datadog", } as const); export type OrganizationIntegrationServiceType = diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-type.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-type.ts index 1c98e174836..3cf68ee9b1d 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-type.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration-type.ts @@ -4,6 +4,7 @@ export const OrganizationIntegrationType = Object.freeze({ Slack: 3, Webhook: 4, Hec: 5, + Datadog: 6, } as const); export type OrganizationIntegrationType = diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration.ts index abbe2271b30..d32c92a460a 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/organization-integration.ts @@ -1,5 +1,6 @@ import { OrganizationIntegrationId } from "@bitwarden/common/types/guid"; +import { DatadogConfiguration } from "./configuration/datadog-configuration"; import { HecConfiguration } from "./configuration/hec-configuration"; import { WebhookConfiguration } from "./configuration/webhook-configuration"; import { OrganizationIntegrationConfiguration } from "./organization-integration-configuration"; @@ -10,14 +11,14 @@ export class OrganizationIntegration { id: OrganizationIntegrationId; type: OrganizationIntegrationType; serviceType: OrganizationIntegrationServiceType; - configuration: HecConfiguration | WebhookConfiguration | null; + configuration: HecConfiguration | WebhookConfiguration | DatadogConfiguration | null; integrationConfiguration: OrganizationIntegrationConfiguration[] = []; constructor( id: OrganizationIntegrationId, type: OrganizationIntegrationType, serviceType: OrganizationIntegrationServiceType, - configuration: HecConfiguration | WebhookConfiguration | null, + configuration: HecConfiguration | WebhookConfiguration | DatadogConfiguration | null, integrationConfiguration: OrganizationIntegrationConfiguration[] = [], ) { this.id = id; diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.spec.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.spec.ts new file mode 100644 index 00000000000..0545f95cb83 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.spec.ts @@ -0,0 +1,184 @@ +import { mock } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; + +import { + OrganizationId, + OrganizationIntegrationConfigurationId, + OrganizationIntegrationId, +} from "@bitwarden/common/types/guid"; + +import { DatadogConfiguration } from "../models/configuration/datadog-configuration"; +import { DatadogTemplate } from "../models/integration-configuration-config/configuration-template/datadog-template"; +import { OrganizationIntegration } from "../models/organization-integration"; +import { OrganizationIntegrationConfiguration } from "../models/organization-integration-configuration"; +import { OrganizationIntegrationConfigurationResponse } from "../models/organization-integration-configuration-response"; +import { OrganizationIntegrationResponse } from "../models/organization-integration-response"; +import { OrganizationIntegrationServiceType } from "../models/organization-integration-service-type"; +import { OrganizationIntegrationType } from "../models/organization-integration-type"; + +import { DatadogOrganizationIntegrationService } from "./datadog-organization-integration-service"; +import { OrganizationIntegrationApiService } from "./organization-integration-api.service"; +import { OrganizationIntegrationConfigurationApiService } from "./organization-integration-configuration-api.service"; + +describe("DatadogOrganizationIntegrationService", () => { + let service: DatadogOrganizationIntegrationService; + const mockIntegrationApiService = mock<OrganizationIntegrationApiService>(); + const mockIntegrationConfigurationApiService = + mock<OrganizationIntegrationConfigurationApiService>(); + const organizationId = "org-1" as OrganizationId; + const integrationId = "int-1" as OrganizationIntegrationId; + const configId = "conf-1" as OrganizationIntegrationConfigurationId; + const serviceType = OrganizationIntegrationServiceType.CrowdStrike; + const url = "https://example.com"; + const apiKey = "token"; + + beforeEach(() => { + service = new DatadogOrganizationIntegrationService( + mockIntegrationApiService, + mockIntegrationConfigurationApiService, + ); + + jest.resetAllMocks(); + }); + + it("should set organization integrations", (done) => { + mockIntegrationApiService.getOrganizationIntegrations.mockResolvedValue([]); + service.setOrganizationIntegrations(organizationId); + const subscription = service.integrations$.subscribe((integrations) => { + expect(integrations).toEqual([]); + subscription.unsubscribe(); + done(); + }); + }); + + it("should save a new Datadog integration", async () => { + service.setOrganizationIntegrations(organizationId); + + const integrationResponse = { + id: integrationId, + type: OrganizationIntegrationType.Datadog, + configuration: JSON.stringify({ url, apiKey, service: serviceType }), + } as OrganizationIntegrationResponse; + + const configResponse = { + id: configId, + template: JSON.stringify({ service: serviceType }), + } as OrganizationIntegrationConfigurationResponse; + + mockIntegrationApiService.createOrganizationIntegration.mockResolvedValue(integrationResponse); + mockIntegrationConfigurationApiService.createOrganizationIntegrationConfiguration.mockResolvedValue( + configResponse, + ); + + await service.saveDatadog(organizationId, serviceType, url, apiKey); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations.length).toBe(1); + expect(integrations[0].id).toBe(integrationId); + expect(integrations[0].serviceType).toBe(serviceType); + }); + + it("should throw error on organization ID mismatch in saveDatadog", async () => { + service.setOrganizationIntegrations("other-org" as OrganizationId); + await expect(service.saveDatadog(organizationId, serviceType, url, apiKey)).rejects.toThrow( + Error("Organization ID mismatch"), + ); + }); + + it("should update an existing Datadog integration", async () => { + service.setOrganizationIntegrations(organizationId); + + const integrationResponse = { + id: integrationId, + type: OrganizationIntegrationType.Datadog, + configuration: JSON.stringify({ url, apiKey, service: serviceType }), + } as OrganizationIntegrationResponse; + + const configResponse = { + id: configId, + template: JSON.stringify({ service: serviceType }), + } as OrganizationIntegrationConfigurationResponse; + + mockIntegrationApiService.updateOrganizationIntegration.mockResolvedValue(integrationResponse); + mockIntegrationConfigurationApiService.updateOrganizationIntegrationConfiguration.mockResolvedValue( + configResponse, + ); + + await service.updateDatadog(organizationId, integrationId, configId, serviceType, url, apiKey); + + const integrations = await firstValueFrom(service.integrations$); + expect(integrations.length).toBe(1); + expect(integrations[0].id).toBe(integrationId); + }); + + it("should throw error on organization ID mismatch in updateDatadog", async () => { + service.setOrganizationIntegrations("other-org" as OrganizationId); + await expect( + service.updateDatadog(organizationId, integrationId, configId, serviceType, url, apiKey), + ).rejects.toThrow(Error("Organization ID mismatch")); + }); + + it("should get integration by id", async () => { + service["_integrations$"].next([ + new OrganizationIntegration( + integrationId, + OrganizationIntegrationType.Datadog, + serviceType, + {} as DatadogConfiguration, + [], + ), + ]); + const integration = await service.getIntegrationById(integrationId); + expect(integration).not.toBeNull(); + expect(integration!.id).toBe(integrationId); + }); + + it("should get integration by service type", async () => { + service["_integrations$"].next([ + new OrganizationIntegration( + integrationId, + OrganizationIntegrationType.Datadog, + serviceType, + {} as DatadogConfiguration, + [], + ), + ]); + const integration = await service.getIntegrationByServiceType(serviceType); + expect(integration).not.toBeNull(); + expect(integration!.serviceType).toBe(serviceType); + }); + + it("should get integration configurations", async () => { + const config = new OrganizationIntegrationConfiguration( + configId, + integrationId, + null, + null, + "", + {} as DatadogTemplate, + ); + + service["_integrations$"].next([ + new OrganizationIntegration( + integrationId, + OrganizationIntegrationType.Datadog, + serviceType, + {} as DatadogConfiguration, + [config], + ), + ]); + const configs = await service.getIntegrationConfigurations(integrationId); + expect(configs).not.toBeNull(); + expect(configs![0].id).toBe(configId); + }); + + it("convertToJson should parse valid JSON", () => { + const obj = service.convertToJson<{ a: number }>('{"a":1}'); + expect(obj).toEqual({ a: 1 }); + }); + + it("convertToJson should return null for invalid JSON", () => { + const obj = service.convertToJson<{ a: number }>("invalid"); + expect(obj).toBeNull(); + }); +}); diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.ts new file mode 100644 index 00000000000..1fd5e9f8c06 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/datadog-organization-integration-service.ts @@ -0,0 +1,350 @@ +import { BehaviorSubject, firstValueFrom, map, Subject, switchMap, takeUntil, zip } from "rxjs"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { + OrganizationId, + OrganizationIntegrationId, + OrganizationIntegrationConfigurationId, +} from "@bitwarden/common/types/guid"; + +import { DatadogConfiguration } from "../models/configuration/datadog-configuration"; +import { DatadogTemplate } from "../models/integration-configuration-config/configuration-template/datadog-template"; +import { OrganizationIntegration } from "../models/organization-integration"; +import { OrganizationIntegrationConfiguration } from "../models/organization-integration-configuration"; +import { OrganizationIntegrationConfigurationRequest } from "../models/organization-integration-configuration-request"; +import { OrganizationIntegrationConfigurationResponse } from "../models/organization-integration-configuration-response"; +import { OrganizationIntegrationRequest } from "../models/organization-integration-request"; +import { OrganizationIntegrationResponse } from "../models/organization-integration-response"; +import { OrganizationIntegrationServiceType } from "../models/organization-integration-service-type"; +import { OrganizationIntegrationType } from "../models/organization-integration-type"; + +import { OrganizationIntegrationApiService } from "./organization-integration-api.service"; +import { OrganizationIntegrationConfigurationApiService } from "./organization-integration-configuration-api.service"; + +export type DatadogModificationFailureReason = { + mustBeOwner: boolean; + success: boolean; +}; + +export class DatadogOrganizationIntegrationService { + private organizationId$ = new BehaviorSubject<OrganizationId | null>(null); + private _integrations$ = new BehaviorSubject<OrganizationIntegration[]>([]); + private destroy$ = new Subject<void>(); + + integrations$ = this._integrations$.asObservable(); + + private fetch$ = this.organizationId$ + .pipe( + switchMap(async (orgId) => { + if (orgId) { + const data$ = await this.setIntegrations(orgId); + return await firstValueFrom(data$); + } else { + return this._integrations$.getValue(); + } + }), + takeUntil(this.destroy$), + ) + .subscribe({ + next: (integrations) => { + this._integrations$.next(integrations); + }, + }); + + constructor( + private integrationApiService: OrganizationIntegrationApiService, + private integrationConfigurationApiService: OrganizationIntegrationConfigurationApiService, + ) {} + + /** + * Sets the organization Id and will trigger the retrieval of the + * integrations for a given org. + * @param orgId + */ + setOrganizationIntegrations(orgId: OrganizationId) { + this.organizationId$.next(orgId); + } + + /** + * Saves a new organization integration and updates the integrations$ observable + * @param organizationId id of the organization + * @param service service type of the integration + * @param url url of the service + * @param apiKey api token + */ + async saveDatadog( + organizationId: OrganizationId, + service: OrganizationIntegrationServiceType, + url: string, + apiKey: string, + ): Promise<DatadogModificationFailureReason> { + if (organizationId != this.organizationId$.getValue()) { + throw new Error("Organization ID mismatch"); + } + + try { + const datadogConfig = new DatadogConfiguration(url, apiKey, service); + const newIntegrationResponse = await this.integrationApiService.createOrganizationIntegration( + organizationId, + new OrganizationIntegrationRequest( + OrganizationIntegrationType.Datadog, + datadogConfig.toString(), + ), + ); + + const newTemplate = new DatadogTemplate(service); + const newIntegrationConfigResponse = + await this.integrationConfigurationApiService.createOrganizationIntegrationConfiguration( + organizationId, + newIntegrationResponse.id, + new OrganizationIntegrationConfigurationRequest(null, null, null, newTemplate.toString()), + ); + + const newIntegration = this.mapResponsesToOrganizationIntegration( + newIntegrationResponse, + newIntegrationConfigResponse, + ); + if (newIntegration !== null) { + this._integrations$.next([...this._integrations$.getValue(), newIntegration]); + } + return { mustBeOwner: false, success: true }; + } catch (error) { + if (error instanceof ErrorResponse && error.statusCode === 404) { + return { mustBeOwner: true, success: false }; + } + throw error; + } + } + + /** + * Updates an existing organization integration and updates the integrations$ observable + * @param organizationId id of the organization + * @param OrganizationIntegrationId id of the organization integration + * @param OrganizationIntegrationConfigurationId id of the organization integration configuration + * @param service service type of the integration + * @param url url of the service + * @param apiKey api token + */ + async updateDatadog( + organizationId: OrganizationId, + OrganizationIntegrationId: OrganizationIntegrationId, + OrganizationIntegrationConfigurationId: OrganizationIntegrationConfigurationId, + service: OrganizationIntegrationServiceType, + url: string, + apiKey: string, + ): Promise<DatadogModificationFailureReason> { + if (organizationId != this.organizationId$.getValue()) { + throw new Error("Organization ID mismatch"); + } + + try { + const datadogConfig = new DatadogConfiguration(url, apiKey, service); + const updatedIntegrationResponse = + await this.integrationApiService.updateOrganizationIntegration( + organizationId, + OrganizationIntegrationId, + new OrganizationIntegrationRequest( + OrganizationIntegrationType.Datadog, + datadogConfig.toString(), + ), + ); + + const updatedTemplate = new DatadogTemplate(service); + const updatedIntegrationConfigResponse = + await this.integrationConfigurationApiService.updateOrganizationIntegrationConfiguration( + organizationId, + OrganizationIntegrationId, + OrganizationIntegrationConfigurationId, + new OrganizationIntegrationConfigurationRequest( + null, + null, + null, + updatedTemplate.toString(), + ), + ); + + const updatedIntegration = this.mapResponsesToOrganizationIntegration( + updatedIntegrationResponse, + updatedIntegrationConfigResponse, + ); + + if (updatedIntegration !== null) { + this._integrations$.next([...this._integrations$.getValue(), updatedIntegration]); + } + return { mustBeOwner: false, success: true }; + } catch (error) { + if (error instanceof ErrorResponse && error.statusCode === 404) { + return { mustBeOwner: true, success: false }; + } + throw error; + } + } + + async deleteDatadog( + organizationId: OrganizationId, + OrganizationIntegrationId: OrganizationIntegrationId, + OrganizationIntegrationConfigurationId: OrganizationIntegrationConfigurationId, + ): Promise<DatadogModificationFailureReason> { + if (organizationId != this.organizationId$.getValue()) { + throw new Error("Organization ID mismatch"); + } + + try { + // delete the configuration first due to foreign key constraint + await this.integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration( + organizationId, + OrganizationIntegrationId, + OrganizationIntegrationConfigurationId, + ); + + // delete the integration + await this.integrationApiService.deleteOrganizationIntegration( + organizationId, + OrganizationIntegrationId, + ); + + // update the local observable + const updatedIntegrations = this._integrations$ + .getValue() + .filter((i) => i.id !== OrganizationIntegrationId); + this._integrations$.next(updatedIntegrations); + + return { mustBeOwner: false, success: true }; + } catch (error) { + if (error instanceof ErrorResponse && error.statusCode === 404) { + return { mustBeOwner: true, success: false }; + } + throw error; + } + } + + /** + * Gets a OrganizationIntegration for an OrganizationIntegrationId + * @param integrationId id of the integration + * @returns OrganizationIntegration or null + */ + // TODO: Move to base class when another service integration type is implemented + async getIntegrationById( + integrationId: OrganizationIntegrationId, + ): Promise<OrganizationIntegration | null> { + return await firstValueFrom( + this.integrations$.pipe( + map((integrations) => integrations.find((i) => i.id === integrationId) || null), + ), + ); + } + + /** + * Gets a OrganizationIntegration for a service type + * @param serviceType type of the service + * @returns OrganizationIntegration or null + */ + // TODO: Move to base class when another service integration type is implemented + async getIntegrationByServiceType( + serviceType: OrganizationIntegrationServiceType, + ): Promise<OrganizationIntegration | null> { + return await firstValueFrom( + this.integrations$.pipe( + map((integrations) => integrations.find((i) => i.serviceType === serviceType) || null), + ), + ); + } + + /** + * Gets a OrganizationIntegrationConfigurations for an integration ID + * @param integrationId id of the integration + * @returns OrganizationIntegration array or null + */ + // TODO: Move to base class when another service integration type is implemented + async getIntegrationConfigurations( + integrationId: OrganizationIntegrationId, + ): Promise<OrganizationIntegrationConfiguration[] | null> { + return await firstValueFrom( + this.integrations$.pipe( + map((integrations) => { + const integration = integrations.find((i) => i.id === integrationId); + return integration ? integration.integrationConfiguration : null; + }), + ), + ); + } + + // TODO: Move to data models to be more explicit for future services + private mapResponsesToOrganizationIntegration( + integrationResponse: OrganizationIntegrationResponse, + configurationResponse: OrganizationIntegrationConfigurationResponse, + ): OrganizationIntegration | null { + const datadogConfig = this.convertToJson<DatadogConfiguration>( + integrationResponse.configuration, + ); + const template = this.convertToJson<DatadogTemplate>(configurationResponse.template); + + if (!datadogConfig || !template) { + return null; + } + + const integrationConfig = new OrganizationIntegrationConfiguration( + configurationResponse.id, + integrationResponse.id, + null, + null, + "", + template, + ); + + return new OrganizationIntegration( + integrationResponse.id, + integrationResponse.type, + datadogConfig.service, + datadogConfig, + [integrationConfig], + ); + } + + // Could possibly be moved to a base service. All services would then assume that the + // integration configuration would always be an array and this datadog specific service + // would just assume a single entry. + private setIntegrations(orgId: OrganizationId) { + const results$ = zip(this.integrationApiService.getOrganizationIntegrations(orgId)).pipe( + switchMap(([responses]) => { + const integrations: OrganizationIntegration[] = []; + const promises: Promise<void>[] = []; + + responses.forEach((integration) => { + if (integration.type === OrganizationIntegrationType.Datadog) { + const promise = this.integrationConfigurationApiService + .getOrganizationIntegrationConfigurations(orgId, integration.id) + .then((response) => { + // datadog events will only have one OrganizationIntegrationConfiguration + const config = response[0]; + + const orgIntegration = this.mapResponsesToOrganizationIntegration( + integration, + config, + ); + + if (orgIntegration !== null) { + integrations.push(orgIntegration); + } + }); + promises.push(promise); + } + }); + return Promise.all(promises).then(() => { + return integrations; + }); + }), + ); + + return results$; + } + + // TODO: Move to base service when necessary + convertToJson<T>(jsonString?: string): T | null { + try { + return JSON.parse(jsonString || "") as T; + } catch { + return null; + } + } +} diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts index 6c6a086e0f5..ad9854c4b25 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts @@ -311,22 +311,24 @@ export class HecOrganizationIntegrationService { const promises: Promise<void>[] = []; responses.forEach((integration) => { - const promise = this.integrationConfigurationApiService - .getOrganizationIntegrationConfigurations(orgId, integration.id) - .then((response) => { - // Hec events will only have one OrganizationIntegrationConfiguration - const config = response[0]; + if (integration.type === OrganizationIntegrationType.Hec) { + const promise = this.integrationConfigurationApiService + .getOrganizationIntegrationConfigurations(orgId, integration.id) + .then((response) => { + // Hec events will only have one OrganizationIntegrationConfiguration + const config = response[0]; - const orgIntegration = this.mapResponsesToOrganizationIntegration( - integration, - config, - ); + const orgIntegration = this.mapResponsesToOrganizationIntegration( + integration, + config, + ); - if (orgIntegration !== null) { - integrations.push(orgIntegration); - } - }); - promises.push(promise); + if (orgIntegration !== null) { + integrations.push(orgIntegration); + } + }); + promises.push(promise); + } }); return Promise.all(promises).then(() => { return integrations; diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts index 74c39613502..8beaae7f10a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, of } from "rxjs"; import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; import { OrganizationIntegrationServiceType } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-service-type"; +import { DatadogOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/datadog-organization-integration-service"; import { HecOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/hec-organization-integration-service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -29,6 +30,7 @@ describe("IntegrationCardComponent", () => { const mockI18nService = mock<I18nService>(); const activatedRoute = mock<ActivatedRoute>(); const mockIntegrationService = mock<HecOrganizationIntegrationService>(); + const mockDatadogIntegrationService = mock<DatadogOrganizationIntegrationService>(); const dialogService = mock<DialogService>(); const toastService = mock<ToastService>(); @@ -53,6 +55,7 @@ describe("IntegrationCardComponent", () => { { provide: I18nService, useValue: mockI18nService }, { provide: ActivatedRoute, useValue: activatedRoute }, { provide: HecOrganizationIntegrationService, useValue: mockIntegrationService }, + { provide: DatadogOrganizationIntegrationService, useValue: mockDatadogIntegrationService }, { provide: ToastService, useValue: toastService }, { provide: DialogService, useValue: dialogService }, ], diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts index 091de63d7a1..3a243f8eb91 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts @@ -13,17 +13,22 @@ import { Observable, Subject, combineLatest, lastValueFrom, takeUntil } from "rx import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration"; import { OrganizationIntegrationServiceType } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-service-type"; +import { OrganizationIntegrationType } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-type"; +import { DatadogOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/datadog-organization-integration-service"; import { HecOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/hec-organization-integration-service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { HecConnectDialogResult, + DatadogConnectDialogResult, HecConnectDialogResultStatus, + DatadogConnectDialogResultStatus, + openDatadogConnectDialog, openHecConnectDialog, } from "../integration-dialog/index"; @@ -64,6 +69,7 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { private dialogService: DialogService, private activatedRoute: ActivatedRoute, private hecOrganizationIntegrationService: HecOrganizationIntegrationService, + private datadogOrganizationIntegrationService: DatadogOrganizationIntegrationService, private toastService: ToastService, private i18nService: I18nService, ) { @@ -131,42 +137,87 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { } async setupConnection() { - // invoke the dialog to connect the integration - const dialog = openHecConnectDialog(this.dialogService, { - data: { - settings: this.integrationSettings, - }, - }); + let dialog: DialogRef<DatadogConnectDialogResult | HecConnectDialogResult, unknown>; - const result = await lastValueFrom(dialog.closed); - - // the dialog was cancelled - if (!result || !result.success) { + if (this.integrationSettings?.integrationType === null) { return; } - try { - if (result.success === HecConnectDialogResultStatus.Delete) { - await this.deleteHec(); - } - } catch { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("failedToDeleteIntegration"), + if (this.integrationSettings?.integrationType === OrganizationIntegrationType.Datadog) { + dialog = openDatadogConnectDialog(this.dialogService, { + data: { + settings: this.integrationSettings, + }, }); - } - try { - if (result.success === HecConnectDialogResultStatus.Edited) { - await this.saveHec(result); + const result = await lastValueFrom(dialog.closed); + + // the dialog was cancelled + if (!result || !result.success) { + return; } - } catch { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("failedToSaveIntegration"), + + try { + if (result.success === HecConnectDialogResultStatus.Delete) { + await this.deleteDatadog(); + } + } catch { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("failedToDeleteIntegration"), + }); + } + + try { + if (result.success === DatadogConnectDialogResultStatus.Edited) { + await this.saveDatadog(result as DatadogConnectDialogResult); + } + } catch { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("failedToSaveIntegration"), + }); + } + } else { + // invoke the dialog to connect the integration + dialog = openHecConnectDialog(this.dialogService, { + data: { + settings: this.integrationSettings, + }, }); + + const result = await lastValueFrom(dialog.closed); + + // the dialog was cancelled + if (!result || !result.success) { + return; + } + + try { + if (result.success === HecConnectDialogResultStatus.Delete) { + await this.deleteHec(); + } + } catch { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("failedToDeleteIntegration"), + }); + } + + try { + if (result.success === HecConnectDialogResultStatus.Edited) { + await this.saveHec(result as HecConnectDialogResult); + } + } catch { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("failedToSaveIntegration"), + }); + } } } @@ -242,6 +293,69 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { }); } + async saveDatadog(result: DatadogConnectDialogResult) { + if (this.isUpdateAvailable) { + // retrieve org integration and configuration ids + const orgIntegrationId = this.integrationSettings.organizationIntegration?.id; + const orgIntegrationConfigurationId = + this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id; + + if (!orgIntegrationId || !orgIntegrationConfigurationId) { + throw Error("Organization Integration ID or Configuration ID is missing"); + } + + // update existing integration and configuration + await this.datadogOrganizationIntegrationService.updateDatadog( + this.organizationId, + orgIntegrationId, + orgIntegrationConfigurationId, + this.integrationSettings.name as OrganizationIntegrationServiceType, + result.url, + result.apiKey, + ); + } else { + // create new integration and configuration + await this.datadogOrganizationIntegrationService.saveDatadog( + this.organizationId, + this.integrationSettings.name as OrganizationIntegrationServiceType, + result.url, + result.apiKey, + ); + } + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("success"), + }); + } + + async deleteDatadog() { + const orgIntegrationId = this.integrationSettings.organizationIntegration?.id; + const orgIntegrationConfigurationId = + this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id; + + if (!orgIntegrationId || !orgIntegrationConfigurationId) { + throw Error("Organization Integration ID or Configuration ID is missing"); + } + + const response = await this.datadogOrganizationIntegrationService.deleteDatadog( + this.organizationId, + orgIntegrationId, + orgIntegrationConfigurationId, + ); + + if (response.mustBeOwner) { + this.showMustBeOwnerToast(); + return; + } + + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("success"), + }); + } + private showMustBeOwnerToast() { this.toastService.showToast({ variant: "error", diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.html new file mode 100644 index 00000000000..c129216b694 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.html @@ -0,0 +1,58 @@ +<form [formGroup]="formGroup" [bitSubmit]="submit"> + <bit-dialog dialogSize="large" [loading]="loading"> + <span bitDialogTitle> + {{ "connectIntegrationButtonDesc" | i18n: connectInfo.settings.name }} + </span> + <div bitDialogContent class="tw-flex tw-flex-col tw-gap-4"> + @if (loading) { + <ng-container #spinner> + <i class="bwi bwi-spinner bwi-lg bwi-spin" aria-hidden="true"></i> + </ng-container> + } + @if (!loading) { + <ng-container> + <bit-form-field> + <bit-label>{{ "url" | i18n }}</bit-label> + <input + bitInput + type="text" + formControlName="url" + placeholder="https://api.<region>.datadoghq.com" + /> + </bit-form-field> + + <bit-form-field> + <bit-label>{{ "apiKey" | i18n }}</bit-label> + <input bitInput type="text" formControlName="apiKey" /> + <bit-hint>{{ "apiKey" | i18n }}</bit-hint> + </bit-form-field> + </ng-container> + } + </div> + <ng-container bitDialogFooter> + <button type="submit" bitButton bitFormButton buttonType="primary" [disabled]="loading"> + @if (isUpdateAvailable) { + {{ "update" | i18n }} + } @else { + {{ "save" | i18n }} + } + </button> + <button type="button" bitButton bitDialogClose buttonType="secondary" [disabled]="loading"> + {{ "cancel" | i18n }} + </button> + + @if (canDelete) { + <div class="tw-ml-auto"> + <button + bitIconButton="bwi-trash" + type="button" + buttonType="danger" + label="'delete' | i18n" + [appA11yTitle]="'delete' | i18n" + [bitAction]="delete" + ></button> + </div> + } + </ng-container> + </bit-dialog> +</form> diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.spec.ts new file mode 100644 index 00000000000..7298087e7e4 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.spec.ts @@ -0,0 +1,171 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { mock } from "jest-mock-extended"; + +import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration"; +import { IntegrationType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +import { + ConnectDatadogDialogComponent, + DatadogConnectDialogParams, + DatadogConnectDialogResult, + DatadogConnectDialogResultStatus, + openDatadogConnectDialog, +} from "./connect-dialog-datadog.component"; + +beforeAll(() => { + // Mock element.animate for jsdom + // the animate function is not available in jsdom, so we provide a mock implementation + // This is necessary for tests that rely on animations + // This mock does not perform any actual animations, it just provides a structure that allows tests + // to run without throwing errors related to missing animate function + if (!HTMLElement.prototype.animate) { + HTMLElement.prototype.animate = function () { + return { + play: () => {}, + pause: () => {}, + finish: () => {}, + cancel: () => {}, + reverse: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + onfinish: null, + oncancel: null, + startTime: 0, + currentTime: 0, + playbackRate: 1, + playState: "idle", + replaceState: "active", + effect: null, + finished: Promise.resolve(), + id: "", + remove: () => {}, + timeline: null, + ready: Promise.resolve(), + } as unknown as Animation; + }; + } +}); + +describe("ConnectDialogDatadogComponent", () => { + let component: ConnectDatadogDialogComponent; + let fixture: ComponentFixture<ConnectDatadogDialogComponent>; + let dialogRefMock = mock<DialogRef<DatadogConnectDialogResult>>(); + const mockI18nService = mock<I18nService>(); + + const integrationMock: Integration = { + name: "Test Integration", + image: "test-image.png", + linkURL: "https://example.com", + imageDarkMode: "test-image-dark.png", + newBadgeExpiration: "2024-12-31", + description: "Test Description", + canSetupConnection: true, + type: IntegrationType.EVENT, + } as Integration; + const connectInfo: DatadogConnectDialogParams = { + settings: integrationMock, // Provide appropriate mock template if needed + }; + + beforeEach(async () => { + dialogRefMock = mock<DialogRef<DatadogConnectDialogResult>>(); + + await TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, SharedModule, BrowserAnimationsModule], + providers: [ + FormBuilder, + { provide: DIALOG_DATA, useValue: connectInfo }, + { provide: DialogRef, useValue: dialogRefMock }, + { provide: I18nPipe, useValue: mock<I18nPipe>() }, + { provide: I18nService, useValue: mockI18nService }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConnectDatadogDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + mockI18nService.t.mockImplementation((key) => key); + }); + + it("should create the component", () => { + expect(component).toBeTruthy(); + }); + + it("should initialize form with empty values", () => { + expect(component.formGroup.value).toEqual({ + url: "", + apiKey: "", + service: "Test Integration", + }); + }); + + it("should have required validators for all fields", () => { + component.formGroup.setValue({ url: "", apiKey: "", service: "" }); + expect(component.formGroup.valid).toBeFalsy(); + + component.formGroup.setValue({ + url: "https://test.com", + apiKey: "token", + service: "Test Service", + }); + expect(component.formGroup.valid).toBeTruthy(); + }); + + it("should test url is at least 7 characters long", () => { + component.formGroup.setValue({ + url: "test", + apiKey: "token", + service: "Test Service", + }); + expect(component.formGroup.valid).toBeFalsy(); + + component.formGroup.setValue({ + url: "https://test.com", + apiKey: "token", + service: "Test Service", + }); + expect(component.formGroup.valid).toBeTruthy(); + }); + + it("should call dialogRef.close with correct result on submit", async () => { + component.formGroup.setValue({ + url: "https://test.com", + apiKey: "token", + service: "Test Service", + }); + + await component.submit(); + + expect(dialogRefMock.close).toHaveBeenCalledWith({ + integrationSettings: integrationMock, + url: "https://test.com", + apiKey: "token", + service: "Test Service", + success: DatadogConnectDialogResultStatus.Edited, + }); + }); +}); + +describe("openDatadogConnectDialog", () => { + it("should call dialogService.open with correct params", () => { + const dialogServiceMock = mock<DialogService>(); + const config: DialogConfig< + DatadogConnectDialogParams, + DialogRef<DatadogConnectDialogResult> + > = { + data: { settings: { name: "Test" } as Integration }, + } as any; + + openDatadogConnectDialog(dialogServiceMock, config); + + expect(dialogServiceMock.open).toHaveBeenCalledWith(ConnectDatadogDialogComponent, config); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.ts new file mode 100644 index 00000000000..d186910d2f7 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-datadog.component.ts @@ -0,0 +1,121 @@ +import { Component, Inject, OnInit } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; + +import { DatadogConfiguration } from "@bitwarden/bit-common/dirt/organization-integrations/models/configuration/datadog-configuration"; +import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration"; +import { HecTemplate } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template"; +import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +export type DatadogConnectDialogParams = { + settings: Integration; +}; + +export interface DatadogConnectDialogResult { + integrationSettings: Integration; + url: string; + apiKey: string; + service: string; + success: DatadogConnectDialogResultStatusType | null; +} + +export const DatadogConnectDialogResultStatus = { + Edited: "edit", + Delete: "delete", +} as const; + +export type DatadogConnectDialogResultStatusType = + (typeof DatadogConnectDialogResultStatus)[keyof typeof DatadogConnectDialogResultStatus]; + +@Component({ + templateUrl: "./connect-dialog-datadog.component.html", + imports: [SharedModule], +}) +export class ConnectDatadogDialogComponent implements OnInit { + loading = false; + datadogConfig: DatadogConfiguration | null = null; + hecTemplate: HecTemplate | null = null; + formGroup = this.formBuilder.group({ + url: ["", [Validators.required, Validators.minLength(7)]], + apiKey: ["", Validators.required], + service: ["", Validators.required], + }); + + constructor( + @Inject(DIALOG_DATA) protected connectInfo: DatadogConnectDialogParams, + protected formBuilder: FormBuilder, + private dialogRef: DialogRef<DatadogConnectDialogResult>, + private dialogService: DialogService, + ) {} + + ngOnInit(): void { + this.datadogConfig = + this.connectInfo.settings.organizationIntegration?.getConfiguration<DatadogConfiguration>() ?? + null; + this.hecTemplate = + this.connectInfo.settings.organizationIntegration?.integrationConfiguration?.[0]?.getTemplate<HecTemplate>() ?? + null; + + this.formGroup.patchValue({ + url: this.datadogConfig?.uri || "", + apiKey: this.datadogConfig?.apiKey || "", + service: this.connectInfo.settings.name, + }); + } + + get isUpdateAvailable(): boolean { + return !!this.datadogConfig; + } + + get canDelete(): boolean { + return !!this.datadogConfig; + } + + submit = async (): Promise<void> => { + if (this.formGroup.invalid) { + this.formGroup.markAllAsTouched(); + return; + } + const result = this.getDatadogConnectDialogResult(DatadogConnectDialogResultStatus.Edited); + + this.dialogRef.close(result); + + return; + }; + + delete = async (): Promise<void> => { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteItem" }, + content: { + key: "deleteItemConfirmation", + }, + type: "warning", + }); + + if (confirmed) { + const result = this.getDatadogConnectDialogResult(DatadogConnectDialogResultStatus.Delete); + this.dialogRef.close(result); + } + }; + + private getDatadogConnectDialogResult( + status: DatadogConnectDialogResultStatusType, + ): DatadogConnectDialogResult { + const formJson = this.formGroup.getRawValue(); + + return { + integrationSettings: this.connectInfo.settings, + url: formJson.url || "", + apiKey: formJson.apiKey || "", + service: formJson.service || "", + success: status, + }; + } +} + +export function openDatadogConnectDialog( + dialogService: DialogService, + config: DialogConfig<DatadogConnectDialogParams, DialogRef<DatadogConnectDialogResult>>, +) { + return dialogService.open<DatadogConnectDialogResult>(ConnectDatadogDialogComponent, config); +} diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/index.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/index.ts index 8c4891b9aa8..9852f3fe5c8 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/index.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/index.ts @@ -1 +1,2 @@ export * from "./connect-dialog/connect-dialog-hec.component"; +export * from "./connect-dialog/connect-dialog-datadog.component"; diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-grid/integration-grid.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-grid/integration-grid.component.spec.ts index f0b371703f6..2908fe0c089 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-grid/integration-grid.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-grid/integration-grid.component.spec.ts @@ -6,6 +6,7 @@ import { of } from "rxjs"; import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration"; +import { DatadogOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/datadog-organization-integration-service"; import { HecOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/hec-organization-integration-service"; import { IntegrationType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -24,6 +25,7 @@ describe("IntegrationGridComponent", () => { let fixture: ComponentFixture<IntegrationGridComponent>; const mockActivatedRoute = mock<ActivatedRoute>(); const mockIntegrationService = mock<HecOrganizationIntegrationService>(); + const mockDatadogIntegrationService = mock<DatadogOrganizationIntegrationService>(); const integrations: Integration[] = [ { name: "Integration 1", @@ -70,6 +72,7 @@ describe("IntegrationGridComponent", () => { useValue: mockActivatedRoute, }, { provide: HecOrganizationIntegrationService, useValue: mockIntegrationService }, + { provide: DatadogOrganizationIntegrationService, useValue: mockDatadogIntegrationService }, { provide: ToastService, useValue: mock<ToastService>(), diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts index c249bf42282..539da9b31b1 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts @@ -4,6 +4,8 @@ import { firstValueFrom, Observable, Subject, switchMap, takeUntil, takeWhile } import { Integration } from "@bitwarden/bit-common/dirt/organization-integrations/models/integration"; import { OrganizationIntegrationServiceType } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-service-type"; +import { OrganizationIntegrationType } from "@bitwarden/bit-common/dirt/organization-integrations/models/organization-integration-type"; +import { DatadogOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/datadog-organization-integration-service"; import { HecOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/hec-organization-integration-service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -226,6 +228,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { // Sets the organization ID which also loads the integrations$ this.organization$.pipe(takeUntil(this.destroy$)).subscribe((org) => { this.hecOrganizationIntegrationService.setOrganizationIntegrations(org.id); + this.datadogOrganizationIntegrationService.setOrganizationIntegrations(org.id); }); // For all existing event based configurations loop through and assign the @@ -253,6 +256,7 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { private accountService: AccountService, private configService: ConfigService, private hecOrganizationIntegrationService: HecOrganizationIntegrationService, + private datadogOrganizationIntegrationService: DatadogOrganizationIntegrationService, ) { this.configService .getFeatureFlag$(FeatureFlag.EventBasedOrganizationIntegrations) @@ -270,10 +274,62 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { type: IntegrationType.EVENT, description: "crowdstrikeEventIntegrationDesc", canSetupConnection: true, + integrationType: 5, // Assuming 5 corresponds to CrowdStrike in OrganizationIntegrationType }; this.integrationsList.push(crowdstrikeIntegration); + + const datadogIntegration: Integration = { + name: OrganizationIntegrationServiceType.Datadog, + // TODO: Update link when help article is published + linkURL: "", + image: "../../../../../../../images/integrations/logo-datadog-color.svg", + type: IntegrationType.EVENT, + description: "datadogEventIntegrationDesc", + canSetupConnection: true, + integrationType: 6, // Assuming 6 corresponds to Datadog in OrganizationIntegrationType + }; + + this.integrationsList.push(datadogIntegration); } + + // For all existing event based configurations loop through and assign the + // organizationIntegration for the correct services. + this.hecOrganizationIntegrationService.integrations$ + .pipe(takeUntil(this.destroy$)) + .subscribe((integrations) => { + // reset all integrations to null first - in case one was deleted + this.integrationsList.forEach((i) => { + if (i.integrationType === OrganizationIntegrationType.Hec) { + i.organizationIntegration = null; + } + }); + + integrations.map((integration) => { + const item = this.integrationsList.find((i) => i.name === integration.serviceType); + if (item) { + item.organizationIntegration = integration; + } + }); + }); + + this.datadogOrganizationIntegrationService.integrations$ + .pipe(takeUntil(this.destroy$)) + .subscribe((integrations) => { + // reset all integrations to null first - in case one was deleted + this.integrationsList.forEach((i) => { + if (i.integrationType === OrganizationIntegrationType.Datadog) { + i.organizationIntegration = null; + } + }); + + integrations.map((integration) => { + const item = this.integrationsList.find((i) => i.name === integration.serviceType); + if (item) { + item.organizationIntegration = integration; + } + }); + }); } ngOnDestroy(): void { this.destroy$.next(); diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts index a8e0899f26d..e3c37b4a42b 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/organization-integrations.module.ts @@ -1,5 +1,6 @@ import { NgModule } from "@angular/core"; +import { DatadogOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/datadog-organization-integration-service"; import { HecOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/hec-organization-integration-service"; import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-api.service"; import { OrganizationIntegrationConfigurationApiService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-configuration-api.service"; @@ -12,6 +13,11 @@ import { OrganizationIntegrationsRoutingModule } from "./organization-integratio @NgModule({ imports: [AdminConsoleIntegrationsComponent, OrganizationIntegrationsRoutingModule], providers: [ + safeProvider({ + provide: DatadogOrganizationIntegrationService, + useClass: DatadogOrganizationIntegrationService, + deps: [OrganizationIntegrationApiService, OrganizationIntegrationConfigurationApiService], + }), safeProvider({ provide: HecOrganizationIntegrationService, useClass: HecOrganizationIntegrationService, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts index bd105fc21e2..43d512439f0 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -9,6 +9,7 @@ import {} from "@bitwarden/web-vault/app/shared"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; +import { DatadogOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/datadog-organization-integration-service"; import { HecOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/hec-organization-integration-service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; @@ -37,6 +38,7 @@ class MockNewMenuComponent {} describe("IntegrationsComponent", () => { let fixture: ComponentFixture<IntegrationsComponent>; const hecOrgIntegrationSvc = mock<HecOrganizationIntegrationService>(); + const datadogOrgIntegrationSvc = mock<DatadogOrganizationIntegrationService>(); const activatedRouteMock = { snapshot: { paramMap: { get: jest.fn() } }, @@ -55,6 +57,7 @@ describe("IntegrationsComponent", () => { { provide: I18nPipe, useValue: mock<I18nPipe>() }, { provide: I18nService, useValue: mockI18nService }, { provide: HecOrganizationIntegrationService, useValue: hecOrgIntegrationSvc }, + { provide: DatadogOrganizationIntegrationService, useValue: datadogOrgIntegrationSvc }, ], }).compileComponents(); fixture = TestBed.createComponent(IntegrationsComponent); From 2127f71f5db8c6eff928402747904d19e320340c Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 7 Oct 2025 08:35:18 -0700 Subject: [PATCH 82/83] feat(sso-config): (Auth) [PM-18470] Pre-populate Key Connector URL (#16536) On the SSO Config page, when Key Connector is a valid option, setup a listener to changes to the Member Decryption Options form radio selection: - If radio selection is Key Connector: set a default URL - If radio selection is NOT Key Connector: clear the URL --- .../bit-web/src/app/auth/sso/sso.component.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts index f68e35bf240..b0f3af4d108 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts @@ -9,7 +9,7 @@ import { Validators, } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs"; +import { concatMap, firstValueFrom, Subject, switchMap, takeUntil } from "rxjs"; import { ControlsOf } from "@bitwarden/angular/types/controls-of"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -34,6 +34,7 @@ import { OrganizationSsoRequest } from "@bitwarden/common/auth/models/request/or import { OrganizationSsoResponse } from "@bitwarden/common/auth/models/response/organization-sso.response"; import { SsoConfigView } from "@bitwarden/common/auth/models/view/sso-config.view"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -203,6 +204,7 @@ export class SsoComponent implements OnInit, OnDestroy { private accountService: AccountService, private organizationApiService: OrganizationApiServiceAbstraction, private toastService: ToastService, + private environmentService: EnvironmentService, ) {} async ngOnInit() { @@ -253,6 +255,32 @@ export class SsoComponent implements OnInit, OnDestroy { .subscribe(); this.showKeyConnectorOptions = this.platformUtilsService.isSelfHost(); + + // Only setup listener if key connector is a possible selection + if (this.showKeyConnectorOptions) { + this.listenForKeyConnectorSelection(); + } + } + + listenForKeyConnectorSelection() { + this.ssoConfigForm?.controls?.memberDecryptionType.valueChanges + .pipe( + switchMap(async (memberDecryptionType) => { + if (memberDecryptionType === MemberDecryptionType.KeyConnector) { + // Pre-populate a default key connector URL (user can still change it) + const env = await firstValueFrom(this.environmentService.environment$); + const webVaultUrl = env.getWebVaultUrl(); + const defaultKeyConnectorUrl = webVaultUrl + "/key-connector/"; + + this.ssoConfigForm.controls.keyConnectorUrl.setValue(defaultKeyConnectorUrl); + } else { + // Otherwise clear the key connector URL + this.ssoConfigForm.controls.keyConnectorUrl.setValue(""); + } + }), + takeUntil(this.destroy$), + ) + .subscribe(); } ngOnDestroy(): void { From 9f0a565241869d7a2a135347af99ff042db84d56 Mon Sep 17 00:00:00 2001 From: Shane Melton <smelton@bitwarden.com> Date: Tue, 7 Oct 2025 08:40:57 -0700 Subject: [PATCH 83/83] [PM-25682] Migrate CipherView and subviews to be TS strict compliant (#16463) * [PM-25682] Remove ts-strict-ignore from Vault view models and update types to be strict * [PM-25682] Ignore ViewEncryptableKeys error for old decrypt methods * [PM-25682] Add null/undefined as possible types for isNull* and other helpers that include null checks internally * [PM-25682] Use patchValue instead of setValue which does not support undefined values * [PM-25682] Add type assertions and other misc. null checks where necessary * [PM-25682] Fix importers specs * [PM-25682] Cleanup card view/details * [PM-25682] Fix cipher view hasAttachment helper * [PM-25682] Cleanup unecessary null assignments in notification.background.spec.ts * [PM-25682] Ensure linkedId is undefined instead of null * [PM-25682] Cleanup misc typing errors * [PM-25682] Make the CipherId required * [PM-25682] Undo CipherId assertions * [PM-25682] Undo brand initial value change * [PM-25682] Update SshKeyView * [PM-25682] Add constructor to Fido2CredentialView * [PM-25682] Prettier * [PM-25682] Fix strict type warnings after merge with main * [PM-25682] Cleanup cipher view spec * [PM-25682] Cleanup new type warnings after merge * [PM-25682] Undo removed eslint-disable-next-line comment * [PM-25682] Fix flaky test * [PM-25682] Use satisfies instead of as for Fido2CredentialAutofillView --- .../notification.background.spec.ts | 10 +- .../background/notification.background.ts | 1 - .../src/vault/app/vault/vault-v2.component.ts | 4 +- .../collections/vault.component.ts | 6 +- .../exposed-passwords-report.component.ts | 3 + .../helpers/risk-insights-data-mappers.ts | 2 +- .../services/password-health.service.ts | 6 +- .../services/risk-insights-report.service.ts | 14 +- libs/common/src/platform/misc/safe-urls.ts | 4 +- libs/common/src/platform/misc/utils.ts | 6 +- .../services/fido2/fido2-autofill-utils.ts | 6 +- .../src/vault/models/domain/attachment.ts | 1 + .../src/vault/models/domain/card.spec.ts | 1 - libs/common/src/vault/models/domain/cipher.ts | 2 + .../vault/models/domain/fido2-credential.ts | 1 + libs/common/src/vault/models/domain/field.ts | 1 + .../src/vault/models/domain/identity.spec.ts | 1 - .../src/vault/models/domain/login-uri.spec.ts | 4 - .../src/vault/models/domain/login.spec.ts | 7 +- .../src/vault/models/view/attachment.view.ts | 30 ++-- .../common/src/vault/models/view/card.view.ts | 70 ++++---- .../src/vault/models/view/cipher.view.spec.ts | 12 +- .../src/vault/models/view/cipher.view.ts | 155 +++++++++--------- .../models/view/fido2-credential.view.ts | 94 +++++++---- .../src/vault/models/view/field.view.ts | 14 +- .../src/vault/models/view/identity.view.ts | 112 ++++++------- .../common/src/vault/models/view/item.view.ts | 6 +- .../src/vault/models/view/login-uri.view.ts | 51 +++--- .../src/vault/models/view/login.view.spec.ts | 5 - .../src/vault/models/view/login.view.ts | 73 ++++----- .../src/vault/models/view/secure-note.view.ts | 18 +- .../src/vault/models/view/ssh-key.view.ts | 31 +--- .../utils/cipher-view-like-utils.spec.ts | 4 + libs/importer/src/importers/base-importer.ts | 4 +- .../src/importers/chrome-csv-importer.spec.ts | 9 +- .../dashlane/dashlane-csv-importer.spec.ts | 16 +- .../importers/firefox-csv-importer.spec.ts | 9 +- .../keeper/keeper-csv-importer.spec.ts | 4 +- .../keeper/keeper-json-importer.spec.ts | 2 +- .../lastpass/lastpass-csv-importer.spec.ts | 16 +- .../src/importers/myki-csv-importer.spec.ts | 4 +- .../importers/nordpass-csv-importer.spec.ts | 6 +- .../onepassword-1pux-importer.spec.ts | 24 +-- .../src/importers/safari-csv-importer.spec.ts | 8 +- .../importers/zohovault-csv-importer.spec.ts | 4 +- ...ditional-options-section.component.spec.ts | 5 +- .../delete-attachment.component.ts | 2 +- .../card-details-section.component.spec.ts | 4 +- .../card-details-section.component.ts | 43 ++--- .../custom-fields/custom-fields.component.ts | 2 +- .../components/identity/identity.component.ts | 2 +- .../item-details-section.component.ts | 2 +- .../services/default-cipher-form.service.ts | 2 +- .../custom-fields-v2.component.ts | 4 +- 54 files changed, 424 insertions(+), 503 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 507789ae7a4..f9e2e1c534f 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -133,19 +133,11 @@ describe("NotificationBackground", () => { expect(cipherView.name).toEqual("example.com"); expect(cipherView.login).toEqual({ - autofillOnPageLoad: null, - fido2Credentials: null, + fido2Credentials: [], password: message.password, - passwordRevisionDate: null, - totp: null, uris: [ { - _canLaunch: null, - _domain: null, - _host: null, - _hostname: null, _uri: message.uri, - match: null, }, ], username: message.username, diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 5bfb12f1932..e27b50f13cd 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1271,7 +1271,6 @@ export default class NotificationBackground { cipherView.folderId = folderId; cipherView.type = CipherType.Login; cipherView.login = loginView; - cipherView.organizationId = null; return cipherView; } diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index aa631c44c64..5f888e081c1 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -45,6 +45,7 @@ import { CipherViewLike, CipherViewLikeUtils, } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; +import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; import { BadgeModule, ButtonModule, @@ -168,6 +169,7 @@ export class VaultV2Component<C extends CipherViewLike> private organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe( map((a) => a?.id), + filterOutNullish(), switchMap((id) => this.organizationService.organizations$(id)), ); @@ -319,7 +321,7 @@ export class VaultV2Component<C extends CipherViewLike> this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault")); const authRequests = await firstValueFrom( - this.authRequestService.getLatestPendingAuthRequest$(), + this.authRequestService.getLatestPendingAuthRequest$()!, ); if (authRequests != null) { this.messagingService.send("openLoginApproval", { diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 3aab02b3b49..b961de9e24c 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -978,7 +978,7 @@ export class VaultComponent implements OnInit, OnDestroy { // Allow restore of an Unassigned Item try { - if (c.id == null) { + if (c.id == null || c.id === "") { throw new Error("Cipher must have an Id to be restored"); } const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); @@ -1211,7 +1211,7 @@ export class VaultComponent implements OnInit, OnDestroy { aType = "Password"; value = cipher.login.password; typeI18nKey = "password"; - } else if (field === "totp") { + } else if (field === "totp" && cipher.login.totp != null) { aType = "TOTP"; const totpResponse = await firstValueFrom(this.totpService.getCode$(cipher.login.totp)); value = totpResponse.code; @@ -1232,7 +1232,7 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if (!cipher.viewPassword) { + if (!cipher.viewPassword || value == null) { return; } diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts index 1a4141c4d68..bf2a528e723 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts @@ -89,6 +89,9 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple private async isPasswordExposed(cv: CipherView): Promise<ReportResult | null> { const { login } = cv; + if (login.password == null) { + return null; + } return await this.auditService.passwordLeaked(login.password).then((exposedCount) => { if (exposedCount > 0) { return { ...cv, exposedXTimes: exposedCount } as ReportResult; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts index 487ac28e963..6afb0ee6815 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts @@ -40,7 +40,7 @@ export function getTrimmedCipherUris(cipher: CipherView): string[] { const uniqueDomains = new Set<string>(); - uris.forEach((u: { uri: string }) => { + uris.forEach((u: { uri: string | undefined }) => { const domain = Utils.getDomain(u.uri) ?? u.uri; uniqueDomains.add(domain); }); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts index 3904c4c3865..2ad9f1c7cfd 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts @@ -29,7 +29,7 @@ export class PasswordHealthService { filter((cipher) => this.isValidCipher(cipher)), mergeMap((cipher) => this.auditService - .passwordLeaked(cipher.login.password) + .passwordLeaked(cipher.login.password!) .then((exposedCount) => ({ cipher, exposedCount })), ), // [FIXME] ExposedDetails is can still return a null @@ -74,11 +74,11 @@ export class PasswordHealthService { // Check the username const userInput = this.isUserNameNotEmpty(cipher) - ? this.extractUsernameParts(cipher.login.username) + ? this.extractUsernameParts(cipher.login.username!) : undefined; const { score } = this.passwordStrengthService.getPasswordStrength( - cipher.login.password, + cipher.login.password!, undefined, // No email available in this context userInput, ); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index dc078d810c2..fcfc7a255df 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -436,13 +436,13 @@ export class RiskInsightsReportService { const weakPassword = this.passwordHealthService.findWeakPasswordDetails(cipher); // Looping over all ciphers needs to happen first to determine reused passwords over all ciphers. // Store in the set and evaluate later - if (passwordUseMap.has(cipher.login.password)) { + if (passwordUseMap.has(cipher.login.password!)) { passwordUseMap.set( - cipher.login.password, - (passwordUseMap.get(cipher.login.password) || 0) + 1, + cipher.login.password!, + (passwordUseMap.get(cipher.login.password!) || 0) + 1, ); } else { - passwordUseMap.set(cipher.login.password, 1); + passwordUseMap.set(cipher.login.password!, 1); } const exposedPassword = exposedDetails.find((x) => x?.cipherId === cipher.id); @@ -466,7 +466,7 @@ export class RiskInsightsReportService { // loop for reused passwords cipherHealthReports.forEach((detail) => { - detail.reusedPasswordCount = passwordUseMap.get(detail.login.password) ?? 0; + detail.reusedPasswordCount = passwordUseMap.get(detail.login.password!) ?? 0; }); return cipherHealthReports; } @@ -514,7 +514,7 @@ export class RiskInsightsReportService { private _buildPasswordUseMap(ciphers: CipherView[]): Map<string, number> { const passwordUseMap = new Map<string, number>(); ciphers.forEach((cipher) => { - const password = cipher.login.password; + const password = cipher.login.password!; passwordUseMap.set(password, (passwordUseMap.get(password) || 0) + 1); }); return passwordUseMap; @@ -686,7 +686,7 @@ export class RiskInsightsReportService { healthData: { weakPasswordDetail: this.passwordHealthService.findWeakPasswordDetails(cipher), exposedPasswordDetail: exposedPassword, - reusedPasswordCount: passwordUseMap.get(cipher.login.password) ?? 0, + reusedPasswordCount: passwordUseMap.get(cipher.login.password!) ?? 0, }, applications: getTrimmedCipherUris(cipher), } as CipherHealthReport; diff --git a/libs/common/src/platform/misc/safe-urls.ts b/libs/common/src/platform/misc/safe-urls.ts index d7223a344e4..f958f92aa19 100644 --- a/libs/common/src/platform/misc/safe-urls.ts +++ b/libs/common/src/platform/misc/safe-urls.ts @@ -17,13 +17,13 @@ const CanLaunchWhitelist = [ ]; export class SafeUrls { - static canLaunch(uri: string): boolean { + static canLaunch(uri: string | null | undefined): boolean { if (Utils.isNullOrWhitespace(uri)) { return false; } for (let i = 0; i < CanLaunchWhitelist.length; i++) { - if (uri.indexOf(CanLaunchWhitelist[i]) === 0) { + if (uri!.indexOf(CanLaunchWhitelist[i]) === 0) { return true; } } diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index c771bee5463..5f977da3979 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -375,7 +375,7 @@ export class Utils { } } - static getDomain(uriString: string): string { + static getDomain(uriString: string | null | undefined): string { if (Utils.isNullOrWhitespace(uriString)) { return null; } @@ -457,7 +457,7 @@ export class Utils { return str == null || typeof str !== "string" || str.trim() === ""; } - static isNullOrEmpty(str: string | null): boolean { + static isNullOrEmpty(str: string | null | undefined): boolean { return str == null || typeof str !== "string" || str == ""; } @@ -479,7 +479,7 @@ export class Utils { return (Object.keys(obj).filter((k) => Number.isNaN(+k)) as K[]).map((k) => obj[k]); } - static getUrl(uriString: string): URL { + static getUrl(uriString: string | undefined | null): URL { if (this.isNullOrWhitespace(uriString)) { return null; } diff --git a/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts index 31f6ce10e01..a58b2d470e6 100644 --- a/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts +++ b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts @@ -27,8 +27,8 @@ export async function getCredentialsForAutofill( cipherId: cipher.id, credentialId: credId, rpId: credential.rpId, - userHandle: credential.userHandle, - userName: credential.userName, - }; + userHandle: credential.userHandle!, + userName: credential.userName!, + } satisfies Fido2CredentialAutofillView; }); } diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 5fff6b32aac..4ace8ce0e77 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -47,6 +47,7 @@ export class Attachment extends Domain { ): Promise<AttachmentView> { const view = await this.decryptObj<Attachment, AttachmentView>( this, + // @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now. new AttachmentView(this), ["fileName"], orgId, diff --git a/libs/common/src/vault/models/domain/card.spec.ts b/libs/common/src/vault/models/domain/card.spec.ts index 5a134651e32..4da62c631d6 100644 --- a/libs/common/src/vault/models/domain/card.spec.ts +++ b/libs/common/src/vault/models/domain/card.spec.ts @@ -63,7 +63,6 @@ describe("Card", () => { expect(view).toEqual({ _brand: "brand", _number: "number", - _subTitle: null, cardholderName: "cardHolder", code: "code", expMonth: "expMonth", diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index c4ee35b2b8f..8ba81c7bbd3 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -161,6 +161,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> { await this.decryptObj<Cipher, CipherView>( this, + // @ts-expect-error Ciphers have optional Ids which are getting swallowed by the ViewEncryptableKeys type + // The ViewEncryptableKeys type should be fixed to allow for optional Ids, but is out of scope for now. model, ["name", "notes"], this.organizationId, diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index a74afc2336d..bdfac9a85ad 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -56,6 +56,7 @@ export class Fido2Credential extends Domain { async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<Fido2CredentialView> { const view = await this.decryptObj<Fido2Credential, Fido2CredentialView>( this, + // @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now. new Fido2CredentialView(), [ "credentialId", diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index f652a2820d4..130d1cc56d5 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -39,6 +39,7 @@ export class Field extends Domain { decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<FieldView> { return this.decryptObj<Field, FieldView>( this, + // @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now. new FieldView(this), ["name", "value"], orgId, diff --git a/libs/common/src/vault/models/domain/identity.spec.ts b/libs/common/src/vault/models/domain/identity.spec.ts index c122c90371f..9fbcb92e4ae 100644 --- a/libs/common/src/vault/models/domain/identity.spec.ts +++ b/libs/common/src/vault/models/domain/identity.spec.ts @@ -112,7 +112,6 @@ describe("Identity", () => { expect(view).toEqual({ _firstName: "mockFirstName", _lastName: "mockLastName", - _subTitle: null, address1: "mockAddress1", address2: "mockAddress2", address3: "mockAddress3", diff --git a/libs/common/src/vault/models/domain/login-uri.spec.ts b/libs/common/src/vault/models/domain/login-uri.spec.ts index cbab41f1472..e67ba771412 100644 --- a/libs/common/src/vault/models/domain/login-uri.spec.ts +++ b/libs/common/src/vault/models/domain/login-uri.spec.ts @@ -56,10 +56,6 @@ describe("LoginUri", () => { const view = await loginUri.decrypt(null); expect(view).toEqual({ - _canLaunch: null, - _domain: null, - _host: null, - _hostname: null, _uri: "uri", match: 3, }); diff --git a/libs/common/src/vault/models/domain/login.spec.ts b/libs/common/src/vault/models/domain/login.spec.ts index dc3cc71fda8..99ceb2b0a3d 100644 --- a/libs/common/src/vault/models/domain/login.spec.ts +++ b/libs/common/src/vault/models/domain/login.spec.ts @@ -2,7 +2,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; -import { UriMatchStrategy, UriMatchStrategySetting } from "../../../models/domain/domain-service"; +import { UriMatchStrategy } from "../../../models/domain/domain-service"; import { LoginData } from "../../models/data/login.data"; import { Login } from "../../models/domain/login"; import { LoginUri } from "../../models/domain/login-uri"; @@ -82,12 +82,7 @@ describe("Login DTO", () => { totp: "encrypted totp", uris: [ { - match: null as UriMatchStrategySetting, _uri: "decrypted uri", - _domain: null as string, - _hostname: null as string, - _host: null as string, - _canLaunch: null as boolean, }, ], autofillOnPageLoad: true, diff --git a/libs/common/src/vault/models/view/attachment.view.ts b/libs/common/src/vault/models/view/attachment.view.ts index 1c796c8f275..ef4a9ed8b27 100644 --- a/libs/common/src/vault/models/view/attachment.view.ts +++ b/libs/common/src/vault/models/view/attachment.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; @@ -10,12 +8,12 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr import { Attachment } from "../domain/attachment"; export class AttachmentView implements View { - id: string = null; - url: string = null; - size: string = null; - sizeName: string = null; - fileName: string = null; - key: SymmetricCryptoKey = null; + id?: string; + url?: string; + size?: string; + sizeName?: string; + fileName?: string; + key?: SymmetricCryptoKey; /** * The SDK returns an encrypted key for the attachment. */ @@ -35,7 +33,7 @@ export class AttachmentView implements View { get fileSize(): number { try { if (this.size != null) { - return parseInt(this.size, null); + return parseInt(this.size); } } catch { // Invalid file size. @@ -71,7 +69,7 @@ export class AttachmentView implements View { fileName: this.fileName, key: this.encryptedKey?.toSdk(), // TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete - decryptedKey: this.key ? this.key.toBase64() : null, + decryptedKey: this.key ? this.key.toBase64() : undefined, }; } @@ -84,13 +82,13 @@ export class AttachmentView implements View { } const view = new AttachmentView(); - view.id = obj.id ?? null; - view.url = obj.url ?? null; - view.size = obj.size ?? null; - view.sizeName = obj.sizeName ?? null; - view.fileName = obj.fileName ?? null; + view.id = obj.id; + view.url = obj.url; + view.size = obj.size; + view.sizeName = obj.sizeName; + view.fileName = obj.fileName; // TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete - view.key = obj.decryptedKey ? SymmetricCryptoKey.fromString(obj.decryptedKey) : null; + view.key = obj.decryptedKey ? SymmetricCryptoKey.fromString(obj.decryptedKey) : undefined; view.encryptedKey = obj.key ? new EncString(obj.key) : undefined; return view; diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index ed02fa68365..9b78ad384c6 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { CardView as SdkCardView } from "@bitwarden/sdk-internal"; @@ -12,45 +10,45 @@ import { ItemView } from "./item.view"; export class CardView extends ItemView implements SdkCardView { @linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 }) - cardholderName: string = null; + cardholderName: string | undefined; @linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" }) - expMonth: string = null; + expMonth: string | undefined; @linkedFieldOption(LinkedId.ExpYear, { sortPosition: 4, i18nKey: "expirationYear" }) - expYear: string = null; + expYear: string | undefined; @linkedFieldOption(LinkedId.Code, { sortPosition: 5, i18nKey: "securityCode" }) - code: string = null; + code: string | undefined; - private _brand: string = null; - private _number: string = null; - private _subTitle: string = null; + private _brand?: string; + private _number?: string; + private _subTitle?: string; - get maskedCode(): string { - return this.code != null ? "•".repeat(this.code.length) : null; + get maskedCode(): string | undefined { + return this.code != null ? "•".repeat(this.code.length) : undefined; } - get maskedNumber(): string { - return this.number != null ? "•".repeat(this.number.length) : null; + get maskedNumber(): string | undefined { + return this.number != null ? "•".repeat(this.number.length) : undefined; } @linkedFieldOption(LinkedId.Brand, { sortPosition: 2 }) - get brand(): string { + get brand(): string | undefined { return this._brand; } - set brand(value: string) { + set brand(value: string | undefined) { this._brand = value; - this._subTitle = null; + this._subTitle = undefined; } @linkedFieldOption(LinkedId.Number, { sortPosition: 1 }) - get number(): string { + get number(): string | undefined { return this._number; } - set number(value: string) { + set number(value: string | undefined) { this._number = value; - this._subTitle = null; + this._subTitle = undefined; } - get subTitle(): string { + get subTitle(): string | undefined { if (this._subTitle == null) { this._subTitle = this.brand; if (this.number != null && this.number.length >= 4) { @@ -69,11 +67,11 @@ export class CardView extends ItemView implements SdkCardView { return this._subTitle; } - get expiration(): string { - const normalizedYear = normalizeExpiryYearFormat(this.expYear); + get expiration(): string | undefined { + const normalizedYear = this.expYear ? normalizeExpiryYearFormat(this.expYear) : undefined; if (!this.expMonth && !normalizedYear) { - return null; + return undefined; } let exp = this.expMonth != null ? ("0" + this.expMonth).slice(-2) : "__"; @@ -82,14 +80,14 @@ export class CardView extends ItemView implements SdkCardView { return exp; } - static fromJSON(obj: Partial<Jsonify<CardView>>): CardView { + static fromJSON(obj: Partial<Jsonify<CardView>> | undefined): CardView { return Object.assign(new CardView(), obj); } // ref https://stackoverflow.com/a/5911300 - static getCardBrandByPatterns(cardNum: string): string { + static getCardBrandByPatterns(cardNum: string | undefined | null): string | undefined { if (cardNum == null || typeof cardNum !== "string" || cardNum.trim() === "") { - return null; + return undefined; } // Visa @@ -146,25 +144,21 @@ export class CardView extends ItemView implements SdkCardView { return "Visa"; } - return null; + return undefined; } /** * Converts an SDK CardView to a CardView. */ - static fromSdkCardView(obj: SdkCardView): CardView | undefined { - if (obj == null) { - return undefined; - } - + static fromSdkCardView(obj: SdkCardView): CardView { const cardView = new CardView(); - cardView.cardholderName = obj.cardholderName ?? null; - cardView.brand = obj.brand ?? null; - cardView.number = obj.number ?? null; - cardView.expMonth = obj.expMonth ?? null; - cardView.expYear = obj.expYear ?? null; - cardView.code = obj.code ?? null; + cardView.cardholderName = obj.cardholderName; + cardView.brand = obj.brand; + cardView.number = obj.number; + cardView.expMonth = obj.expMonth; + cardView.expYear = obj.expYear; + cardView.code = obj.code; return cardView; } diff --git a/libs/common/src/vault/models/view/cipher.view.spec.ts b/libs/common/src/vault/models/view/cipher.view.spec.ts index e9614db6858..2965a9b1c7f 100644 --- a/libs/common/src/vault/models/view/cipher.view.spec.ts +++ b/libs/common/src/vault/models/view/cipher.view.spec.ts @@ -180,15 +180,12 @@ describe("CipherView", () => { folderId: "folderId", collectionIds: ["collectionId"], name: "name", - notes: null, type: CipherType.Login, favorite: true, edit: true, reprompt: CipherRepromptType.None, organizationUseTotp: false, viewPassword: true, - localData: undefined, - permissions: undefined, attachments: [ { id: "attachmentId", @@ -224,7 +221,6 @@ describe("CipherView", () => { passwordHistory: [], creationDate: new Date("2022-01-01T12:00:00.000Z"), revisionDate: new Date("2022-01-02T12:00:00.000Z"), - deletedDate: null, }); }); }); @@ -283,18 +279,12 @@ describe("CipherView", () => { restore: true, delete: true, }, - deletedDate: undefined, creationDate: "2022-01-02T12:00:00.000Z", revisionDate: "2022-01-02T12:00:00.000Z", attachments: [], passwordHistory: [], - login: undefined, - identity: undefined, - card: undefined, - secureNote: undefined, - sshKey: undefined, fields: [], - } as SdkCipherView); + }); }); }); }); diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index b9f717b3a7f..3381f0a47ab 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -1,7 +1,6 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { asUuid, uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; +import { ItemView } from "@bitwarden/common/vault/models/view/item.view"; import { CipherView as SdkCipherView } from "@bitwarden/sdk-internal"; import { View } from "../../../models/view/view"; @@ -26,18 +25,18 @@ import { SshKeyView } from "./ssh-key.view"; export class CipherView implements View, InitializerMetadata { readonly initializerKey = InitializerKey.CipherView; - id: string = null; - organizationId: string | undefined = null; - folderId: string = null; - name: string = null; - notes: string = null; - type: CipherType = null; + id: string = ""; + organizationId?: string; + folderId?: string; + name: string = ""; + notes?: string; + type: CipherType = CipherType.Login; favorite = false; organizationUseTotp = false; - permissions: CipherPermissionsApi = new CipherPermissionsApi(); + permissions?: CipherPermissionsApi = new CipherPermissionsApi(); edit = false; viewPassword = true; - localData: LocalData; + localData?: LocalData; login = new LoginView(); identity = new IdentityView(); card = new CardView(); @@ -46,11 +45,11 @@ export class CipherView implements View, InitializerMetadata { attachments: AttachmentView[] = []; fields: FieldView[] = []; passwordHistory: PasswordHistoryView[] = []; - collectionIds: string[] = null; - revisionDate: Date = null; - creationDate: Date = null; - deletedDate: Date | null = null; - archivedDate: Date | null = null; + collectionIds: string[] = []; + revisionDate: Date; + creationDate: Date; + deletedDate?: Date; + archivedDate?: Date; reprompt: CipherRepromptType = CipherRepromptType.None; // We need a copy of the encrypted key so we can pass it to // the SdkCipherView during encryption @@ -63,6 +62,7 @@ export class CipherView implements View, InitializerMetadata { constructor(c?: Cipher) { if (!c) { + this.creationDate = this.revisionDate = new Date(); return; } @@ -86,7 +86,7 @@ export class CipherView implements View, InitializerMetadata { this.key = c.key; } - private get item() { + private get item(): ItemView | undefined { switch (this.type) { case CipherType.Login: return this.login; @@ -102,10 +102,10 @@ export class CipherView implements View, InitializerMetadata { break; } - return null; + return undefined; } - get subTitle(): string { + get subTitle(): string | undefined { return this.item?.subTitle; } @@ -114,7 +114,7 @@ export class CipherView implements View, InitializerMetadata { } get hasAttachments(): boolean { - return this.attachments && this.attachments.length > 0; + return !!this.attachments && this.attachments.length > 0; } get hasOldAttachments(): boolean { @@ -132,11 +132,11 @@ export class CipherView implements View, InitializerMetadata { return this.fields && this.fields.length > 0; } - get passwordRevisionDisplayDate(): Date { + get passwordRevisionDisplayDate(): Date | undefined { if (this.type !== CipherType.Login || this.login == null) { - return null; + return undefined; } else if (this.login.password == null || this.login.password === "") { - return null; + return undefined; } return this.login.passwordRevisionDate; } @@ -170,23 +170,17 @@ export class CipherView implements View, InitializerMetadata { * Determines if the cipher can be launched in a new browser tab. */ get canLaunch(): boolean { - return this.type === CipherType.Login && this.login.canLaunch; + return this.type === CipherType.Login && this.login!.canLaunch; } linkedFieldValue(id: LinkedIdType) { const linkedFieldOption = this.linkedFieldOptions?.get(id); - if (linkedFieldOption == null) { - return null; + const item = this.item; + if (linkedFieldOption == null || item == null) { + return undefined; } - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const item = this.item; - return this.item[linkedFieldOption.propertyKey as keyof typeof item]; - } - - linkedFieldI18nKey(id: LinkedIdType): string { - return this.linkedFieldOptions.get(id)?.i18nKey; + return item[linkedFieldOption.propertyKey as keyof typeof item]; } // This is used as a marker to indicate that the cipher view object still has its prototype @@ -194,23 +188,31 @@ export class CipherView implements View, InitializerMetadata { return this; } - static fromJSON(obj: Partial<DeepJsonify<CipherView>>): CipherView { + static fromJSON(obj: Partial<DeepJsonify<CipherView>>): CipherView | null { if (obj == null) { return null; } const view = new CipherView(); - const creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); - const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); - const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); - const archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate); - const attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a)); - const fields = obj.fields?.map((f: any) => FieldView.fromJSON(f)); - const passwordHistory = obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph)); - const permissions = CipherPermissionsApi.fromJSON(obj.permissions); - let key: EncString | undefined; + view.type = obj.type ?? CipherType.Login; + view.id = obj.id ?? ""; + view.name = obj.name ?? ""; + if (obj.creationDate) { + view.creationDate = new Date(obj.creationDate); + } + if (obj.revisionDate) { + view.revisionDate = new Date(obj.revisionDate); + } + view.deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate); + view.archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate); + view.attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a)) ?? []; + view.fields = obj.fields?.map((f: any) => FieldView.fromJSON(f)) ?? []; + view.passwordHistory = + obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph)) ?? []; + view.permissions = obj.permissions ? CipherPermissionsApi.fromJSON(obj.permissions) : undefined; if (obj.key != null) { + let key: EncString | undefined; if (typeof obj.key === "string") { // If the key is a string, we need to parse it as EncString key = EncString.fromJSON(obj.key); @@ -218,20 +220,9 @@ export class CipherView implements View, InitializerMetadata { // If the key is already an EncString instance, we can use it directly key = obj.key; } + view.key = key; } - Object.assign(view, obj, { - creationDate: creationDate, - revisionDate: revisionDate, - deletedDate: deletedDate, - archivedDate: archivedDate, - attachments: attachments, - fields: fields, - passwordHistory: passwordHistory, - permissions: permissions, - key: key, - }); - switch (obj.type) { case CipherType.Card: view.card = CardView.fromJSON(obj.card); @@ -264,46 +255,54 @@ export class CipherView implements View, InitializerMetadata { } const cipherView = new CipherView(); - cipherView.id = uuidAsString(obj.id) ?? null; - cipherView.organizationId = uuidAsString(obj.organizationId) ?? null; - cipherView.folderId = uuidAsString(obj.folderId) ?? null; + cipherView.id = uuidAsString(obj.id); + cipherView.organizationId = uuidAsString(obj.organizationId); + cipherView.folderId = uuidAsString(obj.folderId); cipherView.name = obj.name; - cipherView.notes = obj.notes ?? null; + cipherView.notes = obj.notes; cipherView.type = obj.type; cipherView.favorite = obj.favorite; cipherView.organizationUseTotp = obj.organizationUseTotp; - cipherView.permissions = CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions); + cipherView.permissions = obj.permissions + ? CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions) + : undefined; cipherView.edit = obj.edit; cipherView.viewPassword = obj.viewPassword; cipherView.localData = fromSdkLocalData(obj.localData); cipherView.attachments = - obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)) ?? []; - cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? []; + obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)!) ?? []; + cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)!) ?? []; cipherView.passwordHistory = - obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)) ?? []; + obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)!) ?? []; cipherView.collectionIds = obj.collectionIds?.map((i) => uuidAsString(i)) ?? []; - cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); - cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); - cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); - cipherView.archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate); + cipherView.revisionDate = new Date(obj.revisionDate); + cipherView.creationDate = new Date(obj.creationDate); + cipherView.deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate); + cipherView.archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate); cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None; - cipherView.key = EncString.fromJSON(obj.key); + cipherView.key = obj.key ? EncString.fromJSON(obj.key) : undefined; switch (obj.type) { case CipherType.Card: - cipherView.card = CardView.fromSdkCardView(obj.card); + cipherView.card = obj.card ? CardView.fromSdkCardView(obj.card) : new CardView(); break; case CipherType.Identity: - cipherView.identity = IdentityView.fromSdkIdentityView(obj.identity); + cipherView.identity = obj.identity + ? IdentityView.fromSdkIdentityView(obj.identity) + : new IdentityView(); break; case CipherType.Login: - cipherView.login = LoginView.fromSdkLoginView(obj.login); + cipherView.login = obj.login ? LoginView.fromSdkLoginView(obj.login) : new LoginView(); break; case CipherType.SecureNote: - cipherView.secureNote = SecureNoteView.fromSdkSecureNoteView(obj.secureNote); + cipherView.secureNote = obj.secureNote + ? SecureNoteView.fromSdkSecureNoteView(obj.secureNote) + : new SecureNoteView(); break; case CipherType.SshKey: - cipherView.sshKey = SshKeyView.fromSdkSshKeyView(obj.sshKey); + cipherView.sshKey = obj.sshKey + ? SshKeyView.fromSdkSshKeyView(obj.sshKey) + : new SshKeyView(); break; default: break; @@ -354,19 +353,19 @@ export class CipherView implements View, InitializerMetadata { switch (this.type) { case CipherType.Card: - sdkCipherView.card = this.card.toSdkCardView(); + sdkCipherView.card = this.card?.toSdkCardView(); break; case CipherType.Identity: - sdkCipherView.identity = this.identity.toSdkIdentityView(); + sdkCipherView.identity = this.identity?.toSdkIdentityView(); break; case CipherType.Login: - sdkCipherView.login = this.login.toSdkLoginView(); + sdkCipherView.login = this.login?.toSdkLoginView(); break; case CipherType.SecureNote: - sdkCipherView.secureNote = this.secureNote.toSdkSecureNoteView(); + sdkCipherView.secureNote = this.secureNote?.toSdkSecureNoteView(); break; case CipherType.SshKey: - sdkCipherView.sshKey = this.sshKey.toSdkSshKeyView(); + sdkCipherView.sshKey = this.sshKey?.toSdkSshKeyView(); break; default: break; diff --git a/libs/common/src/vault/models/view/fido2-credential.view.ts b/libs/common/src/vault/models/view/fido2-credential.view.ts index 410757ebe30..19e7f5d7e3c 100644 --- a/libs/common/src/vault/models/view/fido2-credential.view.ts +++ b/libs/common/src/vault/models/view/fido2-credential.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { @@ -10,21 +8,55 @@ import { import { ItemView } from "./item.view"; export class Fido2CredentialView extends ItemView { - credentialId: string; - keyType: "public-key"; - keyAlgorithm: "ECDSA"; - keyCurve: "P-256"; - keyValue: string; - rpId: string; - userHandle: string; - userName: string; - counter: number; - rpName: string; - userDisplayName: string; - discoverable: boolean; - creationDate: Date = null; + credentialId!: string; + keyType!: "public-key"; + keyAlgorithm!: "ECDSA"; + keyCurve!: "P-256"; + keyValue!: string; + rpId!: string; + userHandle?: string; + userName?: string; + counter!: number; + rpName?: string; + userDisplayName?: string; + discoverable: boolean = false; + creationDate!: Date; - get subTitle(): string { + constructor(f?: { + credentialId: string; + keyType: "public-key"; + keyAlgorithm: "ECDSA"; + keyCurve: "P-256"; + keyValue: string; + rpId: string; + userHandle?: string; + userName?: string; + counter: number; + rpName?: string; + userDisplayName?: string; + discoverable?: boolean; + creationDate: Date; + }) { + super(); + if (f == null) { + return; + } + this.credentialId = f.credentialId; + this.keyType = f.keyType; + this.keyAlgorithm = f.keyAlgorithm; + this.keyCurve = f.keyCurve; + this.keyValue = f.keyValue; + this.rpId = f.rpId; + this.userHandle = f.userHandle; + this.userName = f.userName; + this.counter = f.counter; + this.rpName = f.rpName; + this.userDisplayName = f.userDisplayName; + this.discoverable = f.discoverable ?? false; + this.creationDate = f.creationDate; + } + + get subTitle(): string | undefined { return this.userDisplayName; } @@ -43,21 +75,21 @@ export class Fido2CredentialView extends ItemView { return undefined; } - const view = new Fido2CredentialView(); - view.credentialId = obj.credentialId; - view.keyType = obj.keyType as "public-key"; - view.keyAlgorithm = obj.keyAlgorithm as "ECDSA"; - view.keyCurve = obj.keyCurve as "P-256"; - view.rpId = obj.rpId; - view.userHandle = obj.userHandle; - view.userName = obj.userName; - view.counter = parseInt(obj.counter); - view.rpName = obj.rpName; - view.userDisplayName = obj.userDisplayName; - view.discoverable = obj.discoverable?.toLowerCase() === "true" ? true : false; - view.creationDate = obj.creationDate ? new Date(obj.creationDate) : null; - - return view; + return new Fido2CredentialView({ + credentialId: obj.credentialId, + keyType: obj.keyType as "public-key", + keyAlgorithm: obj.keyAlgorithm as "ECDSA", + keyCurve: obj.keyCurve as "P-256", + keyValue: obj.keyValue, + rpId: obj.rpId, + userHandle: obj.userHandle, + userName: obj.userName, + counter: parseInt(obj.counter), + rpName: obj.rpName, + userDisplayName: obj.userDisplayName, + discoverable: obj.discoverable?.toLowerCase() === "true", + creationDate: new Date(obj.creationDate), + }); } toSdkFido2CredentialFullView(): Fido2CredentialFullView { diff --git a/libs/common/src/vault/models/view/field.view.ts b/libs/common/src/vault/models/view/field.view.ts index 8c9a923aed2..9f34420a86c 100644 --- a/libs/common/src/vault/models/view/field.view.ts +++ b/libs/common/src/vault/models/view/field.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { FieldView as SdkFieldView, FieldType as SdkFieldType } from "@bitwarden/sdk-internal"; @@ -9,13 +7,13 @@ import { FieldType, LinkedIdType } from "../../enums"; import { Field } from "../domain/field"; export class FieldView implements View { - name: string = null; - value: string = null; - type: FieldType = null; + name?: string; + value?: string; + type: FieldType = FieldType.Text; newField = false; // Marks if the field is new and hasn't been saved showValue = false; showCount = false; - linkedId: LinkedIdType = null; + linkedId?: LinkedIdType; constructor(f?: Field) { if (!f) { @@ -26,8 +24,8 @@ export class FieldView implements View { this.linkedId = f.linkedId; } - get maskedValue(): string { - return this.value != null ? "••••••••" : null; + get maskedValue(): string | undefined { + return this.value != null ? "••••••••" : undefined; } static fromJSON(obj: Partial<Jsonify<FieldView>>): FieldView { diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index 2b863dc5e5f..5fb0d1acba5 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { IdentityView as SdkIdentityView } from "@bitwarden/sdk-internal"; @@ -12,65 +10,65 @@ import { ItemView } from "./item.view"; export class IdentityView extends ItemView implements SdkIdentityView { @linkedFieldOption(LinkedId.Title, { sortPosition: 0 }) - title: string = null; + title: string | undefined; @linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 }) - middleName: string = null; + middleName: string | undefined; @linkedFieldOption(LinkedId.Address1, { sortPosition: 12 }) - address1: string = null; + address1: string | undefined; @linkedFieldOption(LinkedId.Address2, { sortPosition: 13 }) - address2: string = null; + address2: string | undefined; @linkedFieldOption(LinkedId.Address3, { sortPosition: 14 }) - address3: string = null; + address3: string | undefined; @linkedFieldOption(LinkedId.City, { sortPosition: 15, i18nKey: "cityTown" }) - city: string = null; + city: string | undefined; @linkedFieldOption(LinkedId.State, { sortPosition: 16, i18nKey: "stateProvince" }) - state: string = null; + state: string | undefined; @linkedFieldOption(LinkedId.PostalCode, { sortPosition: 17, i18nKey: "zipPostalCode" }) - postalCode: string = null; + postalCode: string | undefined; @linkedFieldOption(LinkedId.Country, { sortPosition: 18 }) - country: string = null; + country: string | undefined; @linkedFieldOption(LinkedId.Company, { sortPosition: 6 }) - company: string = null; + company: string | undefined; @linkedFieldOption(LinkedId.Email, { sortPosition: 10 }) - email: string = null; + email: string | undefined; @linkedFieldOption(LinkedId.Phone, { sortPosition: 11 }) - phone: string = null; + phone: string | undefined; @linkedFieldOption(LinkedId.Ssn, { sortPosition: 7 }) - ssn: string = null; + ssn: string | undefined; @linkedFieldOption(LinkedId.Username, { sortPosition: 5 }) - username: string = null; + username: string | undefined; @linkedFieldOption(LinkedId.PassportNumber, { sortPosition: 8 }) - passportNumber: string = null; + passportNumber: string | undefined; @linkedFieldOption(LinkedId.LicenseNumber, { sortPosition: 9 }) - licenseNumber: string = null; + licenseNumber: string | undefined; - private _firstName: string = null; - private _lastName: string = null; - private _subTitle: string = null; + private _firstName: string | undefined; + private _lastName: string | undefined; + private _subTitle: string | undefined; constructor() { super(); } @linkedFieldOption(LinkedId.FirstName, { sortPosition: 1 }) - get firstName(): string { + get firstName(): string | undefined { return this._firstName; } - set firstName(value: string) { + set firstName(value: string | undefined) { this._firstName = value; - this._subTitle = null; + this._subTitle = undefined; } @linkedFieldOption(LinkedId.LastName, { sortPosition: 4 }) - get lastName(): string { + get lastName(): string | undefined { return this._lastName; } - set lastName(value: string) { + set lastName(value: string | undefined) { this._lastName = value; - this._subTitle = null; + this._subTitle = undefined; } - get subTitle(): string { + get subTitle(): string | undefined { if (this._subTitle == null && (this.firstName != null || this.lastName != null)) { this._subTitle = ""; if (this.firstName != null) { @@ -88,7 +86,7 @@ export class IdentityView extends ItemView implements SdkIdentityView { } @linkedFieldOption(LinkedId.FullName, { sortPosition: 3 }) - get fullName(): string { + get fullName(): string | undefined { if ( this.title != null || this.firstName != null || @@ -111,11 +109,11 @@ export class IdentityView extends ItemView implements SdkIdentityView { return name.trim(); } - return null; + return undefined; } - get fullAddress(): string { - let address = this.address1; + get fullAddress(): string | undefined { + let address = this.address1 ?? ""; if (!Utils.isNullOrWhitespace(this.address2)) { if (!Utils.isNullOrWhitespace(address)) { address += ", "; @@ -131,9 +129,9 @@ export class IdentityView extends ItemView implements SdkIdentityView { return address; } - get fullAddressPart2(): string { + get fullAddressPart2(): string | undefined { if (this.city == null && this.state == null && this.postalCode == null) { - return null; + return undefined; } const city = this.city || "-"; const state = this.state; @@ -146,7 +144,7 @@ export class IdentityView extends ItemView implements SdkIdentityView { return addressPart2; } - get fullAddressForCopy(): string { + get fullAddressForCopy(): string | undefined { let address = this.fullAddress; if (this.city != null || this.state != null || this.postalCode != null) { address += "\n" + this.fullAddressPart2; @@ -157,38 +155,34 @@ export class IdentityView extends ItemView implements SdkIdentityView { return address; } - static fromJSON(obj: Partial<Jsonify<IdentityView>>): IdentityView { + static fromJSON(obj: Partial<Jsonify<IdentityView>> | undefined): IdentityView { return Object.assign(new IdentityView(), obj); } /** * Converts the SDK IdentityView to an IdentityView. */ - static fromSdkIdentityView(obj: SdkIdentityView): IdentityView | undefined { - if (obj == null) { - return undefined; - } - + static fromSdkIdentityView(obj: SdkIdentityView): IdentityView { const identityView = new IdentityView(); - identityView.title = obj.title ?? null; - identityView.firstName = obj.firstName ?? null; - identityView.middleName = obj.middleName ?? null; - identityView.lastName = obj.lastName ?? null; - identityView.address1 = obj.address1 ?? null; - identityView.address2 = obj.address2 ?? null; - identityView.address3 = obj.address3 ?? null; - identityView.city = obj.city ?? null; - identityView.state = obj.state ?? null; - identityView.postalCode = obj.postalCode ?? null; - identityView.country = obj.country ?? null; - identityView.company = obj.company ?? null; - identityView.email = obj.email ?? null; - identityView.phone = obj.phone ?? null; - identityView.ssn = obj.ssn ?? null; - identityView.username = obj.username ?? null; - identityView.passportNumber = obj.passportNumber ?? null; - identityView.licenseNumber = obj.licenseNumber ?? null; + identityView.title = obj.title; + identityView.firstName = obj.firstName; + identityView.middleName = obj.middleName; + identityView.lastName = obj.lastName; + identityView.address1 = obj.address1; + identityView.address2 = obj.address2; + identityView.address3 = obj.address3; + identityView.city = obj.city; + identityView.state = obj.state; + identityView.postalCode = obj.postalCode; + identityView.country = obj.country; + identityView.company = obj.company; + identityView.email = obj.email; + identityView.phone = obj.phone; + identityView.ssn = obj.ssn; + identityView.username = obj.username; + identityView.passportNumber = obj.passportNumber; + identityView.licenseNumber = obj.licenseNumber; return identityView; } diff --git a/libs/common/src/vault/models/view/item.view.ts b/libs/common/src/vault/models/view/item.view.ts index 3954276ca04..d25901f8042 100644 --- a/libs/common/src/vault/models/view/item.view.ts +++ b/libs/common/src/vault/models/view/item.view.ts @@ -1,9 +1,7 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { View } from "../../../models/view/view"; import { LinkedMetadata } from "../../linked-field-option.decorator"; export abstract class ItemView implements View { - linkedFieldOptions: Map<number, LinkedMetadata>; - abstract get subTitle(): string; + linkedFieldOptions?: Map<number, LinkedMetadata>; + abstract get subTitle(): string | undefined; } diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index 49ac9c6278f..bf8dcc83b33 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { LoginUriView as SdkLoginUriView } from "@bitwarden/sdk-internal"; @@ -11,13 +9,13 @@ import { Utils } from "../../../platform/misc/utils"; import { LoginUri } from "../domain/login-uri"; export class LoginUriView implements View { - match: UriMatchStrategySetting = null; + match?: UriMatchStrategySetting; - private _uri: string = null; - private _domain: string = null; - private _hostname: string = null; - private _host: string = null; - private _canLaunch: boolean = null; + private _uri?: string; + private _domain?: string; + private _hostname?: string; + private _host?: string; + private _canLaunch?: boolean; constructor(u?: LoginUri) { if (!u) { @@ -27,59 +25,59 @@ export class LoginUriView implements View { this.match = u.match; } - get uri(): string { + get uri(): string | undefined { return this._uri; } - set uri(value: string) { + set uri(value: string | undefined) { this._uri = value; - this._domain = null; - this._canLaunch = null; + this._domain = undefined; + this._canLaunch = undefined; } - get domain(): string { + get domain(): string | undefined { if (this._domain == null && this.uri != null) { this._domain = Utils.getDomain(this.uri); if (this._domain === "") { - this._domain = null; + this._domain = undefined; } } return this._domain; } - get hostname(): string { + get hostname(): string | undefined { if (this.match === UriMatchStrategy.RegularExpression) { - return null; + return undefined; } if (this._hostname == null && this.uri != null) { this._hostname = Utils.getHostname(this.uri); if (this._hostname === "") { - this._hostname = null; + this._hostname = undefined; } } return this._hostname; } - get host(): string { + get host(): string | undefined { if (this.match === UriMatchStrategy.RegularExpression) { - return null; + return undefined; } if (this._host == null && this.uri != null) { this._host = Utils.getHost(this.uri); if (this._host === "") { - this._host = null; + this._host = undefined; } } return this._host; } - get hostnameOrUri(): string { + get hostnameOrUri(): string | undefined { return this.hostname != null ? this.hostname : this.uri; } - get hostOrUri(): string { + get hostOrUri(): string | undefined { return this.host != null ? this.host : this.uri; } @@ -104,7 +102,10 @@ export class LoginUriView implements View { return this._canLaunch; } - get launchUri(): string { + get launchUri(): string | undefined { + if (this.uri == null) { + return undefined; + } return this.uri.indexOf("://") < 0 && !Utils.isNullOrWhitespace(Utils.getDomain(this.uri)) ? "http://" + this.uri : this.uri; @@ -141,7 +142,7 @@ export class LoginUriView implements View { matchesUri( targetUri: string, equivalentDomains: Set<string>, - defaultUriMatch: UriMatchStrategySetting = null, + defaultUriMatch?: UriMatchStrategySetting, /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */ overrideNeverMatchStrategy?: true, ): boolean { @@ -198,7 +199,7 @@ export class LoginUriView implements View { if (Utils.DomainMatchBlacklist.has(this.domain)) { const domainUrlHost = Utils.getHost(targetUri); - return !Utils.DomainMatchBlacklist.get(this.domain).has(domainUrlHost); + return !Utils.DomainMatchBlacklist.get(this.domain)!.has(domainUrlHost); } return true; diff --git a/libs/common/src/vault/models/view/login.view.spec.ts b/libs/common/src/vault/models/view/login.view.spec.ts index 57e82faf7f1..ec011bed433 100644 --- a/libs/common/src/vault/models/view/login.view.spec.ts +++ b/libs/common/src/vault/models/view/login.view.spec.ts @@ -29,11 +29,6 @@ describe("LoginView", () => { }); describe("fromSdkLoginView", () => { - it("should return undefined when the input is null", () => { - const result = LoginView.fromSdkLoginView(null as unknown as SdkLoginView); - expect(result).toBeUndefined(); - }); - it("should return a LoginView from an SdkLoginView", () => { jest.spyOn(LoginUriView, "fromSdkLoginUriView").mockImplementation(mockFromSdk); diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 44c6ee8f2e9..6f9167cd777 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { LoginView as SdkLoginView } from "@bitwarden/sdk-internal"; import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; @@ -15,15 +13,15 @@ import { LoginUriView } from "./login-uri.view"; export class LoginView extends ItemView { @linkedFieldOption(LinkedId.Username, { sortPosition: 0 }) - username: string = null; + username: string | undefined; @linkedFieldOption(LinkedId.Password, { sortPosition: 1 }) - password: string = null; + password: string | undefined; - passwordRevisionDate?: Date = null; - totp: string = null; + passwordRevisionDate?: Date; + totp: string | undefined; uris: LoginUriView[] = []; - autofillOnPageLoad: boolean = null; - fido2Credentials: Fido2CredentialView[] = null; + autofillOnPageLoad: boolean | undefined; + fido2Credentials: Fido2CredentialView[] = []; constructor(l?: Login) { super(); @@ -35,15 +33,15 @@ export class LoginView extends ItemView { this.autofillOnPageLoad = l.autofillOnPageLoad; } - get uri(): string { - return this.hasUris ? this.uris[0].uri : null; + get uri(): string | undefined { + return this.hasUris ? this.uris[0].uri : undefined; } - get maskedPassword(): string { - return this.password != null ? "••••••••" : null; + get maskedPassword(): string | undefined { + return this.password != null ? "••••••••" : undefined; } - get subTitle(): string { + get subTitle(): string | undefined { // if there's a passkey available, use that as a fallback if (Utils.isNullOrEmpty(this.username) && this.fido2Credentials?.length > 0) { return this.fido2Credentials[0].userName; @@ -60,14 +58,14 @@ export class LoginView extends ItemView { return !Utils.isNullOrWhitespace(this.totp); } - get launchUri(): string { + get launchUri(): string | undefined { if (this.hasUris) { const uri = this.uris.find((u) => u.canLaunch); if (uri != null) { return uri.launchUri; } } - return null; + return undefined; } get hasUris(): boolean { @@ -81,7 +79,7 @@ export class LoginView extends ItemView { matchesUri( targetUri: string, equivalentDomains: Set<string>, - defaultUriMatch: UriMatchStrategySetting = null, + defaultUriMatch?: UriMatchStrategySetting, /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */ overrideNeverMatchStrategy?: true, ): boolean { @@ -94,17 +92,20 @@ export class LoginView extends ItemView { ); } - static fromJSON(obj: Partial<DeepJsonify<LoginView>>): LoginView { - const passwordRevisionDate = - obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); - const uris = obj.uris.map((uri) => LoginUriView.fromJSON(uri)); - const fido2Credentials = obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key)); + static fromJSON(obj: Partial<DeepJsonify<LoginView>> | undefined): LoginView { + if (obj == undefined) { + return new LoginView(); + } - return Object.assign(new LoginView(), obj, { - passwordRevisionDate, - uris, - fido2Credentials, - }); + const loginView = Object.assign(new LoginView(), obj) as LoginView; + + loginView.passwordRevisionDate = + obj.passwordRevisionDate == null ? undefined : new Date(obj.passwordRevisionDate); + loginView.uris = obj.uris?.map((uri) => LoginUriView.fromJSON(uri)) ?? []; + loginView.fido2Credentials = + obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key)) ?? []; + + return loginView; } /** @@ -115,25 +116,21 @@ export class LoginView extends ItemView { * the FIDO2 credentials in encrypted form. We can decrypt them later using a separate * call to client.vault().ciphers().decrypt_fido2_credentials(). */ - static fromSdkLoginView(obj: SdkLoginView): LoginView | undefined { - if (obj == null) { - return undefined; - } - + static fromSdkLoginView(obj: SdkLoginView): LoginView { const loginView = new LoginView(); - loginView.username = obj.username ?? null; - loginView.password = obj.password ?? null; + loginView.username = obj.username; + loginView.password = obj.password; loginView.passwordRevisionDate = - obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); - loginView.totp = obj.totp ?? null; - loginView.autofillOnPageLoad = obj.autofillOnPageLoad ?? null; + obj.passwordRevisionDate == null ? undefined : new Date(obj.passwordRevisionDate); + loginView.totp = obj.totp; + loginView.autofillOnPageLoad = obj.autofillOnPageLoad; loginView.uris = obj.uris ?.filter((uri) => uri.uri != null && uri.uri !== "") - .map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || []; + .map((uri) => LoginUriView.fromSdkLoginUriView(uri)!) || []; // FIDO2 credentials are not decrypted here, they remain encrypted - loginView.fido2Credentials = null; + loginView.fido2Credentials = []; return loginView; } diff --git a/libs/common/src/vault/models/view/secure-note.view.ts b/libs/common/src/vault/models/view/secure-note.view.ts index 5e401961869..85c0d3fd61c 100644 --- a/libs/common/src/vault/models/view/secure-note.view.ts +++ b/libs/common/src/vault/models/view/secure-note.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { SecureNoteView as SdkSecureNoteView } from "@bitwarden/sdk-internal"; @@ -10,7 +8,7 @@ import { SecureNote } from "../domain/secure-note"; import { ItemView } from "./item.view"; export class SecureNoteView extends ItemView implements SdkSecureNoteView { - type: SecureNoteType = null; + type: SecureNoteType = SecureNoteType.Generic; constructor(n?: SecureNote) { super(); @@ -21,24 +19,20 @@ export class SecureNoteView extends ItemView implements SdkSecureNoteView { this.type = n.type; } - get subTitle(): string { - return null; + get subTitle(): string | undefined { + return undefined; } - static fromJSON(obj: Partial<Jsonify<SecureNoteView>>): SecureNoteView { + static fromJSON(obj: Partial<Jsonify<SecureNoteView>> | undefined): SecureNoteView { return Object.assign(new SecureNoteView(), obj); } /** * Converts the SDK SecureNoteView to a SecureNoteView. */ - static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView | undefined { - if (!obj) { - return undefined; - } - + static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView { const secureNoteView = new SecureNoteView(); - secureNoteView.type = obj.type ?? null; + secureNoteView.type = obj.type; return secureNoteView; } diff --git a/libs/common/src/vault/models/view/ssh-key.view.ts b/libs/common/src/vault/models/view/ssh-key.view.ts index 0547eeb7f8e..525608ce274 100644 --- a/libs/common/src/vault/models/view/ssh-key.view.ts +++ b/libs/common/src/vault/models/view/ssh-key.view.ts @@ -1,24 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { SshKeyView as SdkSshKeyView } from "@bitwarden/sdk-internal"; -import { SshKey } from "../domain/ssh-key"; - import { ItemView } from "./item.view"; export class SshKeyView extends ItemView { - privateKey: string = null; - publicKey: string = null; - keyFingerprint: string = null; - - constructor(n?: SshKey) { - super(); - if (!n) { - return; - } - } + privateKey!: string; + publicKey!: string; + keyFingerprint!: string; get maskedPrivateKey(): string { if (!this.privateKey || this.privateKey.length === 0) { @@ -43,23 +32,19 @@ export class SshKeyView extends ItemView { return this.keyFingerprint; } - static fromJSON(obj: Partial<Jsonify<SshKeyView>>): SshKeyView { + static fromJSON(obj: Partial<Jsonify<SshKeyView>> | undefined): SshKeyView { return Object.assign(new SshKeyView(), obj); } /** * Converts the SDK SshKeyView to a SshKeyView. */ - static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView | undefined { - if (!obj) { - return undefined; - } - + static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView { const sshKeyView = new SshKeyView(); - sshKeyView.privateKey = obj.privateKey ?? null; - sshKeyView.publicKey = obj.publicKey ?? null; - sshKeyView.keyFingerprint = obj.fingerprint ?? null; + sshKeyView.privateKey = obj.privateKey; + sshKeyView.publicKey = obj.publicKey; + sshKeyView.keyFingerprint = obj.fingerprint; return sshKeyView; } diff --git a/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts b/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts index 70122ebd27b..56b94fcf3ce 100644 --- a/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts +++ b/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts @@ -298,6 +298,10 @@ describe("CipherViewLikeUtils", () => { (cipherView.attachments as any) = null; expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false); + + cipherView.attachments = []; + + expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false); }); }); diff --git a/libs/importer/src/importers/base-importer.ts b/libs/importer/src/importers/base-importer.ts index 19a8a4828e1..f8acb5e0643 100644 --- a/libs/importer/src/importers/base-importer.ts +++ b/libs/importer/src/importers/base-importer.ts @@ -193,7 +193,6 @@ export abstract class BaseImporter { if (this.isNullOrWhitespace(loginUri.uri)) { return null; } - loginUri.match = null; return [loginUri]; } @@ -205,7 +204,6 @@ export abstract class BaseImporter { if (this.isNullOrWhitespace(loginUri.uri)) { return; } - loginUri.match = null; returnArr.push(loginUri); }); return returnArr.length === 0 ? null : returnArr; @@ -236,7 +234,7 @@ export abstract class BaseImporter { return hostname.startsWith("www.") ? hostname.replace("www.", "") : hostname; } - protected isNullOrWhitespace(str: string): boolean { + protected isNullOrWhitespace(str: string | undefined | null): boolean { return Utils.isNullOrWhitespace(str); } diff --git a/libs/importer/src/importers/chrome-csv-importer.spec.ts b/libs/importer/src/importers/chrome-csv-importer.spec.ts index a7a29094707..df60a6f2647 100644 --- a/libs/importer/src/importers/chrome-csv-importer.spec.ts +++ b/libs/importer/src/importers/chrome-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse app name", csv: androidData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "com.xyz.example.app.android", login: Object.assign(new LoginView(), { username: "username@example.com", @@ -24,7 +21,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -32,9 +28,6 @@ const CipherData = [ title: "should parse password", csv: simplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "www.example.com", login: Object.assign(new LoginView(), { username: "username@example.com", @@ -45,7 +38,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -54,6 +46,7 @@ const CipherData = [ describe("Chrome CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new ChromeCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts b/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts index b8d84a9378a..2dedcec6b2a 100644 --- a/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts +++ b/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts @@ -59,12 +59,12 @@ describe("Dashlane CSV Importer", () => { const cipher = result.ciphers.shift(); expect(cipher.type).toBe(CipherType.Card); expect(cipher.name).toBe("John's savings account"); - expect(cipher.card.brand).toBeNull(); + expect(cipher.card.brand).toBeUndefined(); expect(cipher.card.cardholderName).toBe("John Doe"); expect(cipher.card.number).toBe("accountNumber"); - expect(cipher.card.code).toBeNull(); - expect(cipher.card.expMonth).toBeNull(); - expect(cipher.card.expYear).toBeNull(); + expect(cipher.card.code).toBeUndefined(); + expect(cipher.card.expMonth).toBeUndefined(); + expect(cipher.card.expYear).toBeUndefined(); expect(cipher.fields.length).toBe(4); @@ -112,7 +112,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher.name).toBe("John Doe card"); expect(cipher.identity.fullName).toBe("John Doe"); expect(cipher.identity.firstName).toBe("John"); - expect(cipher.identity.middleName).toBeNull(); + expect(cipher.identity.middleName).toBeUndefined(); expect(cipher.identity.lastName).toBe("Doe"); expect(cipher.identity.licenseNumber).toBe("123123123"); @@ -133,7 +133,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher2.name).toBe("John Doe passport"); expect(cipher2.identity.fullName).toBe("John Doe"); expect(cipher2.identity.firstName).toBe("John"); - expect(cipher2.identity.middleName).toBeNull(); + expect(cipher2.identity.middleName).toBeUndefined(); expect(cipher2.identity.lastName).toBe("Doe"); expect(cipher2.identity.passportNumber).toBe("123123123"); @@ -154,7 +154,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher3.name).toBe("John Doe license"); expect(cipher3.identity.fullName).toBe("John Doe"); expect(cipher3.identity.firstName).toBe("John"); - expect(cipher3.identity.middleName).toBeNull(); + expect(cipher3.identity.middleName).toBeUndefined(); expect(cipher3.identity.lastName).toBe("Doe"); expect(cipher3.identity.licenseNumber).toBe("1234556"); expect(cipher3.identity.state).toBe("DC"); @@ -173,7 +173,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher4.name).toBe("John Doe social_security"); expect(cipher4.identity.fullName).toBe("John Doe"); expect(cipher4.identity.firstName).toBe("John"); - expect(cipher4.identity.middleName).toBeNull(); + expect(cipher4.identity.middleName).toBeUndefined(); expect(cipher4.identity.lastName).toBe("Doe"); expect(cipher4.identity.ssn).toBe("123123123"); diff --git a/libs/importer/src/importers/firefox-csv-importer.spec.ts b/libs/importer/src/importers/firefox-csv-importer.spec.ts index 78bca0599b5..59d2aa9e7a4 100644 --- a/libs/importer/src/importers/firefox-csv-importer.spec.ts +++ b/libs/importer/src/importers/firefox-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse password", csv: simplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com", login: Object.assign(new LoginView(), { username: "foo", @@ -24,7 +21,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -32,9 +28,6 @@ const CipherData = [ title: 'should skip "chrome://FirefoxAccounts"', csv: firefoxAccountsData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com", login: Object.assign(new LoginView(), { username: "foo", @@ -45,7 +38,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -54,6 +46,7 @@ const CipherData = [ describe("Firefox CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new FirefoxCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts b/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts index b326bc5d351..dcaacffc05e 100644 --- a/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts +++ b/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts @@ -51,10 +51,10 @@ describe("Keeper CSV Importer", () => { expect(result != null).toBe(true); const cipher = result.ciphers.shift(); - expect(cipher.login.totp).toBeNull(); + expect(cipher.login.totp).toBeUndefined(); const cipher2 = result.ciphers.shift(); - expect(cipher2.login.totp).toBeNull(); + expect(cipher2.login.totp).toBeUndefined(); const cipher3 = result.ciphers.shift(); expect(cipher3.login.totp).toEqual( diff --git a/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts b/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts index 1141897a044..a9d42369b1e 100644 --- a/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts +++ b/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts @@ -51,7 +51,7 @@ describe("Keeper Json Importer", () => { expect(result != null).toBe(true); const cipher = result.ciphers.shift(); - expect(cipher.login.totp).toBeNull(); + expect(cipher.login.totp).toBeUndefined(); // 2nd Cipher const cipher2 = result.ciphers.shift(); diff --git a/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts b/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts index cabd246fa7e..6515e3959b0 100644 --- a/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts +++ b/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts @@ -37,9 +37,6 @@ Expiration Date:June,2020 Notes:some text ",Credit-card,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "Credit-card", notes: "some text\n", type: 3, @@ -71,11 +68,7 @@ Start Date:, Expiration Date:, Notes:",empty,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "empty", - notes: null, type: 3, card: { expMonth: undefined, @@ -101,11 +94,7 @@ Start Date:, Expiration Date:January, Notes:",noyear,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "noyear", - notes: null, type: 3, card: { cardholderName: "John Doe", @@ -139,11 +128,7 @@ Start Date:, Expiration Date:,2020 Notes:",nomonth,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "nomonth", - notes: null, type: 3, card: { cardholderName: "John Doe", @@ -171,6 +156,7 @@ Notes:",nomonth,,0`, describe("Lastpass CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new LastPassCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/myki-csv-importer.spec.ts b/libs/importer/src/importers/myki-csv-importer.spec.ts index 6f804523ef0..a77e85d134a 100644 --- a/libs/importer/src/importers/myki-csv-importer.spec.ts +++ b/libs/importer/src/importers/myki-csv-importer.spec.ts @@ -468,8 +468,8 @@ describe("Myki CSV Importer", () => { const cipher = result.ciphers.shift(); expect(cipher.name).toEqual("2FA nickname"); - expect(cipher.login.username).toBeNull(); - expect(cipher.login.password).toBeNull(); + expect(cipher.login.username).toBeUndefined(); + expect(cipher.login.password).toBeUndefined(); expect(cipher.login.totp).toBe("someTOTPSeed"); expect(cipher.notes).toEqual("Additional information field content."); diff --git a/libs/importer/src/importers/nordpass-csv-importer.spec.ts b/libs/importer/src/importers/nordpass-csv-importer.spec.ts index e633310e6ee..f04272de012 100644 --- a/libs/importer/src/importers/nordpass-csv-importer.spec.ts +++ b/libs/importer/src/importers/nordpass-csv-importer.spec.ts @@ -17,8 +17,8 @@ const namesTestData = [ fullName: "MyFirstName", expected: Object.assign(new IdentityView(), { firstName: "MyFirstName", - middleName: null, - lastName: null, + middleName: undefined, + lastName: undefined, }), }, { @@ -26,7 +26,7 @@ const namesTestData = [ fullName: "MyFirstName MyLastName", expected: Object.assign(new IdentityView(), { firstName: "MyFirstName", - middleName: null, + middleName: undefined, lastName: "MyLastName", }), }, diff --git a/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts b/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts index 1ca12a9ce69..4ec20ba2a87 100644 --- a/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts +++ b/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts @@ -393,7 +393,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Michael"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Scarn"); expect(identity.address1).toEqual("2120 Mifflin Rd."); expect(identity.state).toEqual("Pennsylvania"); @@ -423,7 +423,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Cash"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Bandit"); expect(identity.state).toEqual("Washington"); expect(identity.country).toEqual("United States of America"); @@ -447,7 +447,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("George"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Engels"); expect(identity.company).toEqual("National Public Library"); expect(identity.phone).toEqual("9995555555"); @@ -472,7 +472,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("David"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Global"); expect(identity.passportNumber).toEqual("76436847"); @@ -499,7 +499,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Chef"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Coldroom"); expect(identity.company).toEqual("Super Cool Store Co."); @@ -523,7 +523,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Jack"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Judd"); expect(identity.ssn).toEqual("131-216-1900"); }); @@ -682,12 +682,12 @@ describe("1Password 1Pux Importer", () => { expect(folders[3].name).toBe("Education"); expect(folders[4].name).toBe("Starter Kit"); - // Check that ciphers have a folder assigned to them - expect(result.ciphers.filter((c) => c.folderId === folders[0].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[1].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[2].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[3].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[4].id).length).toBeGreaterThan(0); + // Check that folder/cipher relationships + expect(result.folderRelationships.filter(([_, f]) => f == 0).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 1).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 2).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 3).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 4).length).toBeGreaterThan(0); }); it("should create collections if part of an organization", async () => { diff --git a/libs/importer/src/importers/safari-csv-importer.spec.ts b/libs/importer/src/importers/safari-csv-importer.spec.ts index 4ca8df23f34..c55117226f9 100644 --- a/libs/importer/src/importers/safari-csv-importer.spec.ts +++ b/libs/importer/src/importers/safari-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse URLs in new CSV format", csv: simplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com (example_user)", login: Object.assign(new LoginView(), { username: "example_user", @@ -33,9 +30,6 @@ const CipherData = [ title: "should parse URLs in old CSV format", csv: oldSimplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com (example_user)", login: Object.assign(new LoginView(), { username: "example_user", @@ -45,6 +39,7 @@ const CipherData = [ uri: "https://example.com", }), ], + totp: null, }), type: 1, }), @@ -54,6 +49,7 @@ const CipherData = [ describe("Safari CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new SafariCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/zohovault-csv-importer.spec.ts b/libs/importer/src/importers/zohovault-csv-importer.spec.ts index d3904fb521a..c82e3e5dcf1 100644 --- a/libs/importer/src/importers/zohovault-csv-importer.spec.ts +++ b/libs/importer/src/importers/zohovault-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse Zoho Vault CSV format", csv: samplezohovaultcsvdata, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "XYZ Test", login: Object.assign(new LoginView(), { username: "email@domain.de", @@ -41,6 +38,7 @@ describe("Zoho Vault CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new ZohoVaultCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts index 6d0266dd0f7..a9a327b90c0 100644 --- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts @@ -86,7 +86,10 @@ describe("AdditionalOptionsSectionComponent", () => { expect(cipherFormProvider.patchCipher).toHaveBeenCalled(); const patchFn = cipherFormProvider.patchCipher.mock.lastCall[0]; - const updated = patchFn(new CipherView()); + const newCipher = new CipherView(); + newCipher.creationDate = newCipher.revisionDate = expectedCipher.creationDate; + + const updated = patchFn(newCipher); expect(updated).toEqual(expectedCipher); }); diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index 1552ef9f30e..60002ca5924 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -66,7 +66,7 @@ export class DeleteAttachmentComponent { await this.cipherService.deleteAttachmentWithServer( this.cipherId, - this.attachment.id, + this.attachment.id!, activeUserId, this.admin, ); diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts index 9233f1fa405..4b0cd0f5f90 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts @@ -67,6 +67,7 @@ describe("CardDetailsSectionComponent", () => { cardView.brand = "Visa"; cardView.expMonth = ""; cardView.code = ""; + cardView.expYear = ""; expect(patchCipherSpy).toHaveBeenCalled(); const patchFn = patchCipherSpy.mock.lastCall[0]; @@ -85,6 +86,7 @@ describe("CardDetailsSectionComponent", () => { cardView.number = ""; cardView.expMonth = ""; cardView.code = ""; + cardView.brand = ""; cardView.expYear = "2022"; expect(patchCipherSpy).toHaveBeenCalled(); @@ -122,8 +124,6 @@ describe("CardDetailsSectionComponent", () => { number, code, brand: cardView.brand, - expMonth: null, - expYear: null, }); }); diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index a71f57481ff..7b8149b6d7b 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -52,12 +52,12 @@ export class CardDetailsSectionComponent implements OnInit { * leaving as just null gets inferred as `unknown` */ cardDetailsForm = this.formBuilder.group({ - cardholderName: null as string | null, - number: null as string | null, - brand: null as string | null, - expMonth: null as string | null, - expYear: null as string | number | null, - code: null as string | null, + cardholderName: "", + number: "", + brand: "", + expMonth: "", + expYear: "" as string | number, + code: "", }); /** Available Card Brands */ @@ -110,16 +110,14 @@ export class CardDetailsSectionComponent implements OnInit { .pipe(takeUntilDestroyed()) .subscribe(({ cardholderName, number, brand, expMonth, expYear, code }) => { this.cipherFormContainer.patchCipher((cipher) => { - const expirationYear = normalizeExpiryYearFormat(expYear); + const expirationYear = normalizeExpiryYearFormat(expYear) ?? ""; - Object.assign(cipher.card, { - cardholderName, - number, - brand, - expMonth, - expYear: expirationYear, - code, - }); + cipher.card.cardholderName = cardholderName; + cipher.card.number = number; + cipher.card.brand = brand; + cipher.card.expMonth = expMonth; + cipher.card.expYear = expirationYear; + cipher.card.code = code; return cipher; }); @@ -167,6 +165,7 @@ export class CardDetailsSectionComponent implements OnInit { expMonth: this.initialValues?.expMonth || "", expYear: this.initialValues?.expYear || "", code: this.initialValues?.code || "", + brand: CardView.getCardBrandByPatterns(this.initialValues?.number) || "", }); } @@ -195,18 +194,4 @@ export class CardDetailsSectionComponent implements OnInit { ); } } - - /** Set form initial form values from the current cipher */ - private setInitialValues(cipherView: CipherView) { - const { cardholderName, number, brand, expMonth, expYear, code } = cipherView.card; - - this.cardDetailsForm.setValue({ - cardholderName: cardholderName, - number: number, - brand: brand, - expMonth: expMonth, - expYear: expYear, - code: code, - }); - } } diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts index 12e83b052bd..013ccd6c87e 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts @@ -386,7 +386,7 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit { fieldView.type = field.type; fieldView.name = field.name; fieldView.value = value; - fieldView.linkedId = field.linkedId; + fieldView.linkedId = field.linkedId ?? undefined; return fieldView; }); diff --git a/libs/vault/src/cipher-form/components/identity/identity.component.ts b/libs/vault/src/cipher-form/components/identity/identity.component.ts index 119ce1caf6e..4c90024e05a 100644 --- a/libs/vault/src/cipher-form/components/identity/identity.component.ts +++ b/libs/vault/src/cipher-form/components/identity/identity.component.ts @@ -172,7 +172,7 @@ export class IdentitySectionComponent implements OnInit { populateFormData(cipherView: CipherView) { const { identity } = cipherView; - this.identityForm.setValue({ + this.identityForm.patchValue({ title: identity.title, firstName: identity.firstName, middleName: identity.middleName, diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts index ced6c809724..8877b4cbcea 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts @@ -312,7 +312,7 @@ export class ItemDetailsSectionComponent implements OnInit { private async initFromExistingCipher(prefillCipher: CipherView) { const { name, folderId, collectionIds } = prefillCipher; - this.itemDetailsForm.setValue({ + this.itemDetailsForm.patchValue({ name: name ? name : (this.initialValues?.name ?? ""), organizationId: prefillCipher.organizationId, // We do not allow changing ownership of an existing cipher. folderId: folderId ? folderId : (this.initialValues?.folderId ?? null), diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index d195ff8b00b..59c583f980b 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -36,7 +36,7 @@ export class DefaultCipherFormService implements CipherFormService { let savedCipher: Cipher; // Creating a new cipher - if (cipher.id == null) { + if (cipher.id == null || cipher.id === "") { const encrypted = await this.cipherService.encrypt(cipher, activeUserId); savedCipher = await this.cipherService.createWithServer(encrypted, config.admin); return await this.cipherService.decrypt(savedCipher, activeUserId); diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts index 2fc35574ba5..7c2afd5029f 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts @@ -44,7 +44,7 @@ import { VaultAutosizeReadOnlyTextArea } from "../../directives/readonly-textare export class CustomFieldV2Component implements OnInit, OnChanges { @Input({ required: true }) cipher!: CipherView; fieldType = FieldType; - fieldOptions: Map<number, LinkedMetadata> | null = null; + fieldOptions: Map<number, LinkedMetadata> | undefined; /** Indexes of hidden fields that are revealed */ revealedHiddenFields: number[] = []; @@ -124,7 +124,7 @@ export class CustomFieldV2Component implements OnInit, OnChanges { case CipherType.Identity: return IdentityView.prototype.linkedFieldOptions; default: - return null; + return undefined; } } }